diff --git a/.gitignore b/.gitignore index 94975bdf..35a25d54 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ yarn-error.log* .idea package-lock.json +/.next/ +/yarn.lock diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index e00595da..00000000 --- a/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/docs/FAQ/firewall-ports.md b/docs/FAQ/firewall-ports.md deleted file mode 100644 index 9c6e2cb5..00000000 --- a/docs/FAQ/firewall-ports.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -id: firewall-ports -title: What firewall ports should I open to use NetBird? -sidebar_position: 1 ---- -### 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 \ No newline at end of file diff --git a/docs/examples/_category_.json b/docs/examples/_category_.json deleted file mode 100644 index 4d81ec12..00000000 --- a/docs/examples/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Examples", - "position": 5 -} diff --git a/docs/examples/netbird-docker.md b/docs/examples/netbird-docker.md deleted file mode 100644 index 6c4ad7c0..00000000 --- a/docs/examples/netbird-docker.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -sidebar_position: 2 ---- - -# 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= -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). \ No newline at end of file diff --git a/docs/getting-started/_category_.json b/docs/getting-started/_category_.json deleted file mode 100644 index 4bd4025b..00000000 --- a/docs/getting-started/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Getting Started", - "position": 4 -} diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md deleted file mode 100644 index 7999c22a..00000000 --- a/docs/getting-started/quickstart.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -sidebar_position: 1 -title: Quickstart Guide ---- - -Step-by-step video guide on YouTube: - -
- -
-
-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. - - ![](/img/getting-started/auth.png) - -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. - - ![](/img/getting-started/empty-peers.png) - -3. Choose your machine operating system (in our case it is ```Linux```) and proceed with the installation steps. - - ![](/img/getting-started/add-peer.png) - -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`. - > - - ![](/img/getting-started/systray.png) - -5. At this point a browser window pops up starting a device registration process. Click confirm and follow the steps if required. - - ![](/img/getting-started/device-confirmation.png) - -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 - - ![](/img/getting-started/netbird-up.png) - -8. Return to ```Peers``` and you should notice 2 new machines with status ```online``` - - ![](/img/getting-started/peers.png) - -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. - -
- -- 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) - - diff --git a/docs/how-to-guides/_category_.json b/docs/how-to-guides/_category_.json deleted file mode 100644 index 9529feb5..00000000 --- a/docs/how-to-guides/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "How-to Guides", - "position": 3 -} diff --git a/docs/integrations/_category_.json b/docs/integrations/_category_.json deleted file mode 100644 index 731c2471..00000000 --- a/docs/integrations/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Integrations", - "position": 4 -} diff --git a/docs/integrations/identity-providers/_category_.json b/docs/integrations/identity-providers/_category_.json deleted file mode 100644 index b1cc6fa3..00000000 --- a/docs/integrations/identity-providers/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Identity Providers", - "position": 4 -} diff --git a/docs/integrations/identity-providers/self-hosted/_category_.json b/docs/integrations/identity-providers/self-hosted/_category_.json deleted file mode 100644 index 9889c7bc..00000000 --- a/docs/integrations/identity-providers/self-hosted/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Self-Hosted NetBird", - "position": 4 -} diff --git a/docs/integrations/identity-providers/self-hosted/auth0.md b/docs/integrations/identity-providers/self-hosted/auth0.md deleted file mode 100644 index 53c0e9c2..00000000 --- a/docs/integrations/identity-providers/self-hosted/auth0.md +++ /dev/null @@ -1,91 +0,0 @@ ---- -id: using-netbird-with-auth0 -title: Using NetBird with Auth0 -sidebar_position: 2 -tags: -- integrations -- idp -- auth0 -- oidc -- how-to ---- - -This guide is a part of the [NetBird Self-hosting Guide](/getting-started/self-hosting) and explains how to integrate **self-hosted** NetBird with [Auth0](https://auth0.com/). - -Auth0 is a flexible, drop-in solution to add authentication and authorization services to your applications. -It is a 3rd party managed service and can't be self-hosted. Auth0 is the right choice if you don't want to manage an Identity Provider (IDP) -instance on your own. - -:::tip self-hosted idp -If you prefer to have full control over authentication and authorization of your NetBird network, there are good -self-hosted alternatives to the managed Auth0 service like [Keycloak](/integrations/identity-providers/self-hosted/using-netbird-with-keycloak). -::: - -### Step 1: Create Auth0 account -To create an Auth0 account, sign up at [https://auth0.com](https://auth0.com/). - -There are five properties of the **`setup.env`** file that we will configure in this guide: -- `NETBIRD_AUTH_CLIENT_ID` -- `NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT` -- `NETBIRD_USE_AUTH0` -- `NETBIRD_AUTH_AUDIENCE` -- `NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID` (Optional) - -### Step 2: Create and configure Auth0 application - -This Auth0 application will be used to authorize access to NetBird Dashboard (Web UI). - -- Follow the steps in the [Auth0 React SDK Guide](https://auth0.com/docs/quickstart/spa/react/01-login#configure-auth0) -up until "Install the Auth0 React SDK". -- Use **`https://YOUR DOMAIN`** as: `Allowed Callback URLs`, `Allowed Logout URLs`, `Allowed Web Origins`, `Allowed Origins (CORS)` - :::caution - Make sure that **`Token Endpoint Authentication Method`** is set to **`None`**. - ::: - -- Use **`Client ID`** to set ```NETBIRD_AUTH_CLIENT_ID``` property in the `setup.env` file. -- Use **`Domain`** to configure ```NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT``` property in the `setup.env` file like so: - ``` - https:///.well-known/openid-configuration - ``` - :::caution - Double-check if the endpoint returns a JSON response by calling it from your browser. - ::: - -### Step 3: Create and configure Auth0 API - -This Auth0 API will be used to access NetBird Management Service API. - -- Follow the steps in the [Auth0 Create An API](https://auth0.com/docs/quickstart/backend/golang#create-an-api). -- Use API **`Identifier`** to set ```NETBIRD_AUTH_AUDIENCE``` property in the `setup.env` file. -- Set ```NETBIRD_USE_AUTH0``` to `true`in the `setup.env` file. - -### Step 4: Enable Interactive SSO Login (Optional) - -The [Interactive SSO Login feature](/getting-started/installation#running-netbird-with-sso-login) allows for machine -authorization with your Identity Provider. This feature can be used as an alternative to [setup keys](/overview/setup-keys) -and is optional. - -You can enable it by following these steps: -- Log in to your Auth0 account https://manage.auth0.com/ -- Go to `Applications` (left-hand menu) -- Click `Create Application` button (top right) -- Fill in the form with the following values: - - Name: `Interactive Login` - - Application type: `Native` -- Click `Create` - -![](/img/integrations/identity-providers/self-hosted/auth0-create-interactive-login-app.png) - -- Click `Settings` tab -- Copy **`Client ID`** to `NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID` in the `setup.env` file - -![](/img/integrations/identity-providers/self-hosted/auth0-interactive-login-settings.png) - -- Scroll down to the `Advanced Settings` section -- Enable **`Device Code`** -- Click `Save Changes` - -![](/img/integrations/identity-providers/self-hosted/auth0-grant-types.png) - -### Step 4: Continue with the self-hosting guide -You can now continue with the [NetBird Self-hosting Guide](/getting-started/self-hosting#step-3-configure-identity-provider). \ No newline at end of file diff --git a/docs/integrations/identity-providers/self-hosted/available-integrations.md b/docs/integrations/identity-providers/self-hosted/available-integrations.md deleted file mode 100644 index 86f85a26..00000000 --- a/docs/integrations/identity-providers/self-hosted/available-integrations.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -id: available-idp-integrations -title: Available IDP Integrations -sidebar_position: 1 ---- - -There are a few Identity Provider options that you can choose to run a self-hosted version NetBird. - -:::tip OpenID -NetBird supports generic OpenID (OIDC) protocol allowing for the integration with any IDP that follows the specification. -::: - -List of available guides: -- [Auth0](/integrations/identity-providers/self-hosted/using-netbird-with-auth0) (managed service) -- [Azure AD](/integrations/identity-providers/self-hosted/using-netbird-with-azure-ad) (managed service) -- [Keycloak](/integrations/identity-providers/self-hosted/using-netbird-with-keycloak) -- [Zitadel](/integrations/identity-providers/self-hosted/using-netbird-with-zitadel) diff --git a/docs/integrations/identity-providers/self-hosted/keycloak.md b/docs/integrations/identity-providers/self-hosted/keycloak.md deleted file mode 100644 index 57aa41ae..00000000 --- a/docs/integrations/identity-providers/self-hosted/keycloak.md +++ /dev/null @@ -1,238 +0,0 @@ ---- -id: using-netbird-with-keycloak -title: Using NetBird with Keycloak -sidebar_position: 3 -tags: -- integrations -- idp -- keycloak -- oidc -- how-to ---- - -This guide is a part of the [NetBird Self-hosting Guide](/getting-started/self-hosting) and explains how to integrate -**self-hosted** NetBird with [Keycloak](https://www.keycloak.org/). - -Keycloak is an open source software product to allow single sign-on with Identity and Access Management aimed at modern applications and services. - -:::tip managed idp -If you prefer not to self-host an Identity and Access Management solution, then you could use a managed alternative like -[Auth0](/integrations/identity-providers/self-hosted/using-netbird-with-auth0). -::: - -The following guide is an adapted version of the original -[Keycloak on Docker](https://www.keycloak.org/getting-started/getting-started-docker) guide from the official website. - -### Expected Result - -After completing this guide, you can log in to your self-hosted NetBird Dashboard and add your machines -to your network using the [Interactive SSO Login feature](/getting-started/installation#running-netbird-with-sso-login) -over Keycloak. - -![](/img/integrations/identity-providers/self-hosted/keycloak-auth-grant.gif) - -### Step 1: Check your Keycloak Instance - -For this guide, you need a fully configured Keycloak instance running with SSL. - -We assume that your Keycloak instance is available at **`https://YOUR-KEYCLOAK-HOST-AND_PORT`**. -Feel free to change the port if you have configured Keycloak with a different one. - -Most of the OIDC software requires SSL for production use. -We encourage you to comply with this requirement to make the world more secure 😊. - -### Step 2: Create a realm - -To create a realm you need to: - -- Open the Keycloak Admin Console -- Hover the mouse over the dropdown in the top-left corner where it says `Master`, then click on `Create Realm` -- Fill in the form with the following values: - - Realm name: `netbird` -- Click `Create` -- -![](/img/integrations/identity-providers/self-hosted/keycloak-create-realm.png) - -### Step 3: Create a user - -In this step we will create a NetBird administrator user. - -- Open the Keycloak Admin Console -- Make sure, that the selected realm is `Netbird` -- Click `Users` (left-hand menu) -- Click `Create new user` -- Fill in the form with the following values: - - Username: `netbird` -- Click `Create` - -![](/img/integrations/identity-providers/self-hosted/keycloak-create-user.png) - -The user will need an initial password set to be able to log in. To do this: -- Click `Credentials` tab -- Click `Set password` button -- Fill in the password form with a password -- Set the `Temporary` field to `Off` to prevent having to update password on first login -- Click `Save` - -![](/img/integrations/identity-providers/self-hosted/keycloak-set-password.png) - -### Step 4: Create a NetBird client - -In this step we will create NetBird application client and register with the Keycloak instance. - -- Open the Keycloak Admin Console -- Make sure, that the selected realm is `Netbird` -- Click `Clients` -- Click `Create client` button -- Fill in the form with the following values and click Next: - - Client Type: `OpenID Connect` - - Client ID: `netbird-client` -- Your newly client `netbird-client` will be used later to set `NETBIRD_AUTH_CLIENT_ID` in the `setup.env` - -![](/img/integrations/identity-providers/self-hosted/keycloak-create-client.png) - -- Check the checkboxes as on the screenshot below and click Save - -![](/img/integrations/identity-providers/self-hosted/keycloak-enable-auth.png) - -### Step 5: Adjust NetBird client access settings - -In this step we will configure NetBird application client access with the NetBird URLs. - -- Open the Keycloak Admin Console -- Make sure, that the selected realm is `Netbird` -- Click `Clients` -- Choose `netbird-client` from the list -- Go to `Access Settings` section -- Fill in the fields with the following values: - - Root URL: `https://YOUR DOMAIN/` (this is the NetBird Dashboard root URL) - - Valid redirect URIs: `https://YOUR DOMAIN/*` - - Valid post logout redirect URIs: `https://YOUR DOMAIN/*` - - Web origins: `+` - - Click `Save` - -![](/img/integrations/identity-providers/self-hosted/keycloak-access-settings.png) - -### Step 6: Create a NetBird client scope - -In this step, we will create and configure the NetBird client audience for Keycloak to add it to the generated JWT tokens. - -- Open the Keycloak Admin Console -- Make sure, that the selected realm is `Netbird` -- Click `Client scopes` (left-hand menu) -- Click `Create client scope` button -- Fill in the form with the following values: - - Name: `api` - - Type: `Default` - - Protocol: `OpenID Connect` -- Click `Save` - -![](/img/integrations/identity-providers/self-hosted/keycloak-create-client-scope.png) - -- While in the newly created Client Scope, switch to the `Mappers` tab -- Click `Configure a new mapper` -- Choose the `Audience` mapping - -![](/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper.png) - -- Fill in the form with the following values: - - Name: `Audience for NetBird Management API` - - Included Client Audience: `netbird-client` - - Add to access token: `On` - - Click `Save` - -![](/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper-2.png) - -### Step 7: Add client scope to NetBird client - -- Open the Keycloak Admin Console -- Make sure, that the selected realm is `Netbird` -- Click `Clients` -- Choose `netbird-client` from the list -- Switch to `Client scopes` tab -- Click `Add client scope` button -- Choose `api` -- Click `Add` choosing `Default` -- The value `netbird-client` will be used as audience - -![](/img/integrations/identity-providers/self-hosted/keycloack-add-client-scope.png) - -### Step 8: Create a NetBird-Backend client - -In this step we will create NetBird backend client and register with the Keycloak instance. - -- Open the Keycloak Admin Console -- Make sure, that the selected realm is `Netbird` -- Click `Clients` -- Click `Create client` button -- Fill in the form with the following values and click Next: - - Client Type: `OpenID Connect` - - Client ID: `netbird-backend` -- Your newly client `netbird-backend` will be used later to set `KeycloakClientCredentials` in the `management.json` - -![](/img/integrations/identity-providers/self-hosted/keycloak-create-backend-client.png) - -- Check the checkboxes as on the screenshot below and click Save - -![](/img/integrations/identity-providers/self-hosted/keycloak-backend-client-auth.png) - -The client will need secret to authenticate. To do this: -- Click `Credentials` tab -- Copy `client secret` will be used later to set `ClientSecret` in the `management.json` - -![](/img/integrations/identity-providers/self-hosted/keycloak-backend-client-credentials.png) - -### Step 9: Add manage-users role to netbird-backend - -- Open the Keycloak Admin Console -- Make sure, that the selected realm is `Netbird` -- Click `Clients` -- Choose `netbird-backend` from the list -- Switch to `Service accounts roles` tab -- Click `Assign roles` button -- Select `Filter by clients` and search for `manage-users` - -![](/img/integrations/identity-providers/self-hosted/keycloak-service-account-role.png) - -- Check the role checkbox and click assign - -![](/img/integrations/identity-providers/self-hosted/keycloak-add-role.png) - -Your authority OIDC configuration will be available under: -``` -https:///realms/netbird/.well-known/openid-configuration -``` -:::caution -Double-check if the endpoint returns a JSON response by calling it from your browser. -::: - -- Set properties in the `setup.env` file: - - NETBIRD_AUTH_OIDC_CONFIGURATION_ENDPOINT=`https:///realms/netbird/.well-known/openid-configuration`. - - NETBIRD_AUTH_CLIENT_ID=`netbird-client` - - NETBIRD_AUTH_AUDIENCE=`netbird-client` - - NETBIRD_AUTH_DEVICE_AUTH_CLIENT_ID=`netbird-client`. Optional, - it enables the [Interactive SSO Login feature](/getting-started/installation#running-netbird-with-sso-login) (Oauth 2.0 Device Authorization Flow) - -- You can now continue with the [NetBird Self-hosting Guide](/getting-started/self-hosting#step-3-configure-identity-provider). - -- Set property `IdpManagerConfig` in the `management.json` file with: - :::caution - The file management.json is created automatically. Please refer [here](/getting-started/self-hosting#step-5-run-configuration-script) for more information. - ::: - - ```json - { - "ManagerType": "keycloak", - "KeycloakClientCredentials": { - "ClientID": "netbird-backend", - "ClientSecret": "", - "GrantType": "client_credentials", - "TokenEndpoint": "https:///realms/netbird/protocol/openid-connect/token", - "AdminEndpoint": "https:///admin/realms/netbird" - } - } - ``` - -:::note -Make sure that your Keycloak instance use HTTPS. Otherwise, the setup won't work. -::: \ No newline at end of file diff --git a/docs/introduction.md b/docs/introduction.md deleted file mode 100644 index a5b5ab41..00000000 --- a/docs/introduction.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: introduction -slug: / -sidebar_position: 1 ---- - -# 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. diff --git a/docs/other/_category_.json b/docs/other/_category_.json deleted file mode 100644 index d7ef8267..00000000 --- a/docs/other/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Other", - "position": 8 -} diff --git a/docs/overview/_category_.json b/docs/overview/_category_.json deleted file mode 100644 index d5352d28..00000000 --- a/docs/overview/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "How NetBird Works", - "position": 2 -} diff --git a/docs/overview/architecture.md b/docs/overview/architecture.md deleted file mode 100644 index 92439532..00000000 --- a/docs/overview/architecture.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -sidebar_position: 1 ---- - -# 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. - -

- high-level-dia -

- -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. - -

- high-level-dia -

- -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 100.64.0.0/10. -* **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. Coming Soon. -* **Managing private DNS.** [DNS](/overview/dns) allows referring to each of the peers with a fully qualified domain name (FQDN). Coming Soon. -* **Monitoring network activity.** Coming Soon. -* **Wireguard key rotation.** Coming Soon. - -The Management service runs in the cloud NetBird-managed. It can also be self-hosted. - -

- management-dia -

- -### 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. - -:::important - -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. - -::: - -### 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. - -

- signal-dia -

- -:::important - -Messages that are sent over Signal are **peer-to-peer encrypted**, so Signal can't see the contents. - -::: - -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. - -

- relay-dia -

- -:::important - -Similar to Signal, traffic that flows through the Relay can't be decrypted due to the **Wireguard peer-to-peer encryption**. - -::: - -It runs in the cloud or can be self-hosted. - -### STUN (NAT Traversal) diff --git a/docs/overview/dns.md b/docs/overview/dns.md deleted file mode 100644 index 9dcc7a57..00000000 --- a/docs/overview/dns.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -sidebar_position: 5 ---- - -# Private DNS \ No newline at end of file diff --git a/docs/overview/general-flow.md b/docs/overview/general-flow.md deleted file mode 100644 index bd2a4d34..00000000 --- a/docs/overview/general-flow.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -sidebar_position: 2 ---- - -# 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 100.64.0.0/10. -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. - diff --git a/docs/reference/_category_.json b/docs/reference/_category_.json deleted file mode 100644 index 3ce2f2b7..00000000 --- a/docs/reference/_category_.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "label": "Reference", - "position": 6 -} diff --git a/docusaurus.config.js b/docusaurus.config.js deleted file mode 100644 index 9eeecfee..00000000 --- a/docusaurus.config.js +++ /dev/null @@ -1,91 +0,0 @@ -// @ts-check -// Note: type annotations allow type checking and IDEs autocompletion - -const lightCodeTheme = require('prism-react-renderer/themes/github'); -const darkCodeTheme = require('prism-react-renderer/themes/dracula'); - -/** @type {import('@docusaurus/types').Config} */ -const config = { - title: 'NetBird Documentation', - tagline: '', - url: 'https://netbird.io', - baseUrl: '/docs/', - onBrokenLinks: 'throw', - onBrokenMarkdownLinks: 'warn', - favicon: '/img/favicon.ico', - organizationName: 'netbirdio', - projectName: 'netbird', - - presets: [ - [ - '@docusaurus/preset-classic', - ({ - docs: { - sidebarPath: require.resolve('./sidebars.js'), - routeBasePath: '/', - editUrl: 'https://github.com/netbirdio/docs/tree/main', - }, - theme: { - customCss: require.resolve('./src/css/custom.css'), - }, - googleAnalytics: { - trackingID: 'UA-188381981-2', - anonymizeIP: true, - }, - }) - ], - ], - - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - ({ - navbar: { - title: '', - logo: { - alt: 'netbird Logo', - src: '/img/logo.svg', - srcDark: '/img/logo-dark.svg', - href: '/', - }, - items: [ - { - to: 'https://netbird.io/', - label: 'Home', - position: 'right', - target: '_self' - }, // or position: 'right' - { - type: 'doc', - docId: 'introduction', - position: 'right', - label: 'Docs', - }, - { - to: 'https://netbird.io/blog/', - label: 'Blog', - position: 'right', - target: '_self' - }, - { - href: 'https://github.com/netbirdio/netbird', - label: 'GitHub', - position: 'right', - }, - ], - }, - prism: { - theme: lightCodeTheme, - darkTheme: darkCodeTheme, - }, - announcementBar: { - id: 'announcementBar-docs', - content: - '⭐ If you like NetBird, give it a star on GitHub', - backgroundColor: '#e3e3e3', - textColor: '#091E42', - isCloseable: true, - }, - }), -}; - -module.exports = config; diff --git a/generator/api.json/.swagger-codegen-ignore b/generator/api.json/.swagger-codegen-ignore new file mode 100644 index 00000000..c5fa491b --- /dev/null +++ b/generator/api.json/.swagger-codegen-ignore @@ -0,0 +1,23 @@ +# Swagger Codegen Ignore +# Generated by swagger-codegen https://github.com/swagger-api/swagger-codegen + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell Swagger Codgen to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/generator/api.json/.swagger-codegen/VERSION b/generator/api.json/.swagger-codegen/VERSION new file mode 100644 index 00000000..34ec317a --- /dev/null +++ b/generator/api.json/.swagger-codegen/VERSION @@ -0,0 +1 @@ +3.0.42 \ No newline at end of file diff --git a/static/.nojekyll b/generator/api.json/README.md similarity index 100% rename from static/.nojekyll rename to generator/api.json/README.md diff --git a/generator/api.json/openapi.json b/generator/api.json/openapi.json new file mode 100644 index 00000000..b0a40828 --- /dev/null +++ b/generator/api.json/openapi.json @@ -0,0 +1,3271 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "NetBird REST API", + "description" : "API to manipulate groups, rules, policies and retrieve information about peers and users", + "version" : "0.0.1" + }, + "servers" : [ { + "url" : "https://api.netbird.io", + "description" : "Default server" + } ], + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ], + "tags" : [ { + "name" : "Users", + "description" : "Interact with and view information about users." + }, { + "name" : "Tokens", + "description" : "Interact with and view information about tokens." + }, { + "name" : "Peers", + "description" : "Interact with and view information about peers." + }, { + "name" : "Setup Keys", + "description" : "Interact with and view information about setup keys." + }, { + "name" : "Groups", + "description" : "Interact with and view information about groups." + }, { + "name" : "Rules", + "description" : "Interact with and view information about rules." + }, { + "name" : "Policies", + "description" : "Interact with and view information about policies." + }, { + "name" : "Routes", + "description" : "Interact with and view information about routes." + }, { + "name" : "DNS", + "description" : "Interact with and view information about DNS configuration." + }, { + "name" : "Events", + "description" : "View information about the account and network events." + }, { + "name" : "Accounts", + "description" : "View information about the accounts." + } ], + "paths" : { + "/api/accounts" : { + "get" : { + "tags" : [ "Accounts" ], + "summary" : "List all Accounts", + "description" : "Returns a list of accounts of a user. Always returns a list of one account.", + "operationId" : "getAccounts", + "responses" : { + "200" : { + "description" : "A JSON array of accounts", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Account" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/accounts/{accountId}" : { + "put" : { + "tags" : [ "Accounts" ], + "summary" : "Update an Account", + "description" : "Update information about an account", + "operationId" : "updateAccount", + "parameters" : [ { + "name" : "accountId", + "in" : "path", + "description" : "The unique identifier of an account", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "update an account", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AccountSettings" + } + } + } + }, + "responses" : { + "200" : { + "description" : "An Account object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Account" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/users" : { + "get" : { + "tags" : [ "Users" ], + "summary" : "List all Users", + "description" : "Returns a list of all users", + "operationId" : "getUsers", + "parameters" : [ { + "name" : "service_user", + "in" : "query", + "description" : "Filters users and returns either regular users or service users", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "boolean" + } + } ], + "responses" : { + "200" : { + "description" : "A JSON array of Users", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/User" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "Users" ], + "summary" : "Create a User", + "description" : "Creates a new service user or sends an invite to a regular user", + "operationId" : "createUser", + "requestBody" : { + "description" : "User invite information", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UserCreateRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A User object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/User" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/users/{userId}" : { + "put" : { + "tags" : [ "Users" ], + "summary" : "Update a User", + "description" : "Update information about a User", + "operationId" : "updateUser", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "The unique identifier of a user", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "User update", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/UserRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A User object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/User" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Users" ], + "summary" : "Delete a User", + "description" : "Delete a User", + "operationId" : "deleteUser", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "The unique identifier of a user", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/users/{userId}/tokens" : { + "get" : { + "tags" : [ "Tokens" ], + "summary" : "List all Tokens", + "description" : "Returns a list of all tokens for a user", + "operationId" : "getTokens", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "The unique identifier of a user", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A JSON Array of PersonalAccessTokens", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PersonalAccessToken" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "Tokens" ], + "summary" : "Create a Token", + "description" : "Create a new token for a user", + "operationId" : "createToken", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "The unique identifier of a user", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "PersonalAccessToken create parameters", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PersonalAccessTokenRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "The token in plain text", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PersonalAccessTokenGenerated" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/users/{userId}/tokens/{tokenId}" : { + "get" : { + "tags" : [ "Tokens" ], + "summary" : "Retrieve a Token", + "description" : "Returns a specific token for a user", + "operationId" : "getToken", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "The unique identifier of a user", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "tokenId", + "in" : "path", + "description" : "The unique identifier of a token", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A PersonalAccessTokens Object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PersonalAccessToken" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Tokens" ], + "summary" : "Delete a Token", + "description" : "Delete a token for a user", + "operationId" : "deleteToken", + "parameters" : [ { + "name" : "userId", + "in" : "path", + "description" : "The unique identifier of a user", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "tokenId", + "in" : "path", + "description" : "The unique identifier of a token", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/peers" : { + "get" : { + "tags" : [ "Peers" ], + "summary" : "List all Peers", + "description" : "Returns a list of all peers", + "operationId" : "getPeers", + "responses" : { + "200" : { + "description" : "A JSON Array of Peers", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Peer" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/peers/{peerId}" : { + "get" : { + "tags" : [ "Peers" ], + "summary" : "Retrieve a Peer", + "description" : "Get information about a peer", + "operationId" : "getPeer", + "parameters" : [ { + "name" : "peerId", + "in" : "path", + "description" : "The unique identifier of a peer", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A Peer object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Peer" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "Peers" ], + "summary" : "Update a Peer", + "description" : "Update information about a peer", + "parameters" : [ { + "name" : "peerId", + "in" : "path", + "description" : "The unique identifier of a peer", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "update a peer", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PeerRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Peer object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Peer" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Peers" ], + "summary" : "Delete a Peer", + "description" : "Delete a peer", + "parameters" : [ { + "name" : "peerId", + "in" : "path", + "description" : "The unique identifier of a peer", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/setup-keys" : { + "get" : { + "tags" : [ "Setup Keys" ], + "summary" : "List all Setup Keys", + "description" : "Returns a list of all Setup Keys", + "responses" : { + "200" : { + "description" : "A JSON Array of Setup keys", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/SetupKey" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "Setup Keys" ], + "summary" : "Create a Setup Key", + "description" : "Creates a Setup Key", + "requestBody" : { + "description" : "New Setup Key request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SetupKeyRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Setup Keys Object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SetupKey" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/setup-keys/{keyId}" : { + "get" : { + "tags" : [ "Setup Keys" ], + "summary" : "Retrieve a Setup Key", + "description" : "Get information about a Setup Key", + "parameters" : [ { + "name" : "keyId", + "in" : "path", + "description" : "The unique identifier of a setup key", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A Setup Key object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SetupKey" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "Setup Keys" ], + "summary" : "Update a Setup Key", + "description" : "Update information about a Setup Key", + "parameters" : [ { + "name" : "keyId", + "in" : "path", + "description" : "The unique identifier of a setup key", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "update to Setup Key", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SetupKeyRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Setup Key object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/SetupKey" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/groups" : { + "get" : { + "tags" : [ "Groups" ], + "summary" : "List all Groups", + "description" : "Returns a list of all Groups", + "responses" : { + "200" : { + "description" : "A JSON Array of Groups", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Group" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "Groups" ], + "summary" : "Create a Group", + "description" : "Creates a Group", + "requestBody" : { + "description" : "New Group request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/GroupRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Group Object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Group" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/groups/{groupId}" : { + "get" : { + "tags" : [ "Groups" ], + "summary" : "Retrieve a Group", + "description" : "Get information about a Group", + "parameters" : [ { + "name" : "groupId", + "in" : "path", + "description" : "The unique identifier of a group", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A Group object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Group" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "Groups" ], + "summary" : "Update a Group", + "description" : "Update/Replace a Group", + "parameters" : [ { + "name" : "groupId", + "in" : "path", + "description" : "The unique identifier of a group", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Update Group request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/GroupRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Group object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Group" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Groups" ], + "summary" : "Delete a Group", + "description" : "Delete a Group", + "parameters" : [ { + "name" : "groupId", + "in" : "path", + "description" : "The unique identifier of a group", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/rules" : { + "get" : { + "tags" : [ "Rules" ], + "summary" : "List all Rules", + "description" : "Returns a list of all Rules", + "responses" : { + "200" : { + "description" : "A JSON Array of Rules", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Rule" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "Rules" ], + "summary" : "Create a Rule", + "description" : "Creates a Rule", + "requestBody" : { + "description" : "New Rule request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RuleRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Rule Object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Rule" + } + } + } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/rules/{ruleId}" : { + "get" : { + "tags" : [ "Rules" ], + "summary" : "Retrieve a Rule", + "description" : "Get information about a Rules", + "parameters" : [ { + "name" : "ruleId", + "in" : "path", + "description" : "The unique identifier of a rule", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A Rule object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Rule" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "Rules" ], + "summary" : "Update a Rule", + "description" : "Update/Replace a Rule", + "parameters" : [ { + "name" : "ruleId", + "in" : "path", + "description" : "The unique identifier of a rule", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Update Rule request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RuleRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Rule object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Rule" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Rules" ], + "summary" : "Delete a Rule", + "description" : "Delete a Rule", + "parameters" : [ { + "name" : "ruleId", + "in" : "path", + "description" : "The unique identifier of a rule", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/policies" : { + "get" : { + "tags" : [ "Policies" ], + "summary" : "List all Policies", + "description" : "Returns a list of all Policies", + "responses" : { + "200" : { + "description" : "A JSON Array of Policies", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Policy" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "Policies" ], + "summary" : "Create a Policy", + "description" : "Creates a Policy", + "requestBody" : { + "description" : "New Policy request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PolicyMinimum" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Policy Object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Policy" + } + } + } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/policies/{policyId}" : { + "get" : { + "tags" : [ "Policies" ], + "summary" : "Retrieve a Policy", + "description" : "Get information about a Policies", + "parameters" : [ { + "name" : "policyId", + "in" : "path", + "description" : "The unique identifier of a policy", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A Policy object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Policy" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "Policies" ], + "summary" : "Update a Policy", + "description" : "Update/Replace a Policy", + "parameters" : [ { + "name" : "policyId", + "in" : "path", + "description" : "The unique identifier of a policy", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Update Policy request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/PolicyMinimum" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Policy object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Policy" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Policies" ], + "summary" : "Delete a Policy", + "description" : "Delete a Policy", + "parameters" : [ { + "name" : "policyId", + "in" : "path", + "description" : "The unique identifier of a policy", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/routes" : { + "get" : { + "tags" : [ "Routes" ], + "summary" : "List all Routes", + "description" : "Returns a list of all routes", + "responses" : { + "200" : { + "description" : "A JSON Array of Routes", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Route" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "Routes" ], + "summary" : "Create a Route", + "description" : "Creates a Route", + "requestBody" : { + "description" : "New Routes request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RouteRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Route Object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Route" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/routes/{routeId}" : { + "get" : { + "tags" : [ "Routes" ], + "summary" : "Retrieve a Route", + "description" : "Get information about a Routes", + "parameters" : [ { + "name" : "routeId", + "in" : "path", + "description" : "The unique identifier of a route", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A Route object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Route" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "Routes" ], + "summary" : "Update a Route", + "description" : "Update/Replace a Route", + "parameters" : [ { + "name" : "routeId", + "in" : "path", + "description" : "The unique identifier of a route", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Update Route request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/RouteRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Route object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Route" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "Routes" ], + "summary" : "Delete a Route", + "description" : "Delete a Route", + "parameters" : [ { + "name" : "routeId", + "in" : "path", + "description" : "The unique identifier of a route", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/dns/nameservers" : { + "get" : { + "tags" : [ "DNS" ], + "summary" : "List all Nameserver Groups", + "description" : "Returns a list of all Nameserver Groups", + "responses" : { + "200" : { + "description" : "A JSON Array of Nameserver Groups", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/NameserverGroup" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "post" : { + "tags" : [ "DNS" ], + "summary" : "Create a Nameserver Group", + "description" : "Creates a Nameserver Group", + "requestBody" : { + "description" : "New Nameserver Groups request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/NameserverGroupRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Nameserver Groups Object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/NameserverGroup" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/dns/nameservers/{nsgroupId}" : { + "get" : { + "tags" : [ "DNS" ], + "summary" : "Retrieve a Nameserver Group", + "description" : "Get information about a Nameserver Groups", + "parameters" : [ { + "name" : "nsgroupId", + "in" : "path", + "description" : "The unique identifier of a Nameserver Group", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "A Nameserver Group object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/NameserverGroup" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "DNS" ], + "summary" : "Update a Nameserver Group", + "description" : "Update/Replace a Nameserver Group", + "parameters" : [ { + "name" : "nsgroupId", + "in" : "path", + "description" : "The unique identifier of a Nameserver Group", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Update Nameserver Group request", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/NameserverGroupRequest" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A Nameserver Group object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/NameserverGroup" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "delete" : { + "tags" : [ "DNS" ], + "summary" : "Delete a Nameserver Group", + "description" : "Delete a Nameserver Group", + "parameters" : [ { + "name" : "nsgroupId", + "in" : "path", + "description" : "The unique identifier of a Nameserver Group", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Delete status code", + "content" : { } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/dns/settings" : { + "get" : { + "tags" : [ "DNS" ], + "summary" : "Retrieve DNS Settings", + "description" : "Returns a DNS settings object", + "responses" : { + "200" : { + "description" : "A JSON Object of DNS Setting", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/DNSSettings" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + }, + "put" : { + "tags" : [ "DNS" ], + "summary" : "Update DNS Settings", + "description" : "Updates a DNS settings object", + "requestBody" : { + "description" : "A DNS settings object", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DNSSettings" + } + } + } + }, + "responses" : { + "200" : { + "description" : "A JSON Object of DNS Setting", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/DNSSettings" + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + }, + "/api/events" : { + "get" : { + "tags" : [ "Events" ], + "summary" : "List all Events", + "description" : "Returns a list of all events", + "responses" : { + "200" : { + "description" : "A JSON Array of Events", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Event" + } + } + } + } + }, + "400" : { + "description" : "Bad Request", + "content" : { } + }, + "401" : { + "description" : "Requires authentication", + "content" : { } + }, + "403" : { + "description" : "Forbidden", + "content" : { } + }, + "500" : { + "description" : "Internal Server Error", + "content" : { } + } + }, + "security" : [ { + "BearerAuth" : [ ] + }, { + "TokenAuth" : [ ] + } ] + } + } + }, + "components" : { + "schemas" : { + "Account" : { + "required" : [ "id", "settings" ], + "properties" : { + "id" : { + "type" : "string", + "description" : "Account ID", + "example" : "ch8i4ug6lnn4g9hqv7l0" + }, + "settings" : { + "$ref" : "#/components/schemas/AccountSettings" + } + } + }, + "AccountSettings" : { + "required" : [ "peer_login_expiration", "peer_login_expiration_enabled" ], + "properties" : { + "peer_login_expiration_enabled" : { + "type" : "boolean", + "description" : "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).", + "example" : true + }, + "peer_login_expiration" : { + "type" : "integer", + "description" : "Period of time after which peer login expires (seconds).", + "example" : 43200 + } + } + }, + "User" : { + "required" : [ "auto_groups", "email", "id", "name", "role", "status" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "User ID", + "example" : "google-oauth2|277474792786460067937" + }, + "email" : { + "type" : "string", + "description" : "User's email address", + "example" : "demo@netbird.io" + }, + "name" : { + "type" : "string", + "description" : "User's name from idp provider", + "example" : "Tom Schulz" + }, + "role" : { + "type" : "string", + "description" : "User's NetBird account role", + "example" : "admin" + }, + "status" : { + "type" : "string", + "description" : "User's status", + "example" : "active", + "enum" : [ "active", "invited", "disabled" ] + }, + "auto_groups" : { + "type" : "array", + "description" : "Groups to auto-assign to peers registered by this user", + "items" : { + "type" : "string", + "example" : "devs" + } + }, + "is_current" : { + "type" : "boolean", + "description" : "Is true if authenticated user is the same as this user", + "readOnly" : true, + "example" : true + }, + "is_service_user" : { + "type" : "boolean", + "description" : "Is true if this user is a service user", + "readOnly" : true, + "example" : false + } + } + }, + "UserRequest" : { + "required" : [ "auto_groups", "role" ], + "type" : "object", + "properties" : { + "role" : { + "type" : "string", + "description" : "User's NetBird account role", + "example" : "admin" + }, + "auto_groups" : { + "type" : "array", + "description" : "Groups to auto-assign to peers registered by this user", + "items" : { + "type" : "string", + "example" : "devs" + } + } + } + }, + "UserCreateRequest" : { + "required" : [ "auto_groups", "is_service_user", "role" ], + "type" : "object", + "properties" : { + "email" : { + "type" : "string", + "description" : "User's Email to send invite to", + "example" : "demo@netbird.io" + }, + "name" : { + "type" : "string", + "description" : "User's full name", + "example" : "Tom Schulz" + }, + "role" : { + "type" : "string", + "description" : "User's NetBird account role", + "example" : "admin" + }, + "auto_groups" : { + "type" : "array", + "description" : "Groups to auto-assign to peers registered by this user", + "items" : { + "type" : "string", + "example" : "devs" + } + }, + "is_service_user" : { + "type" : "boolean", + "description" : "Is true if this user is a service user", + "example" : false + } + } + }, + "PeerMinimum" : { + "required" : [ "id", "name" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Peer ID", + "example" : "chacbco6lnnbn6cg5s90" + }, + "name" : { + "type" : "string", + "description" : "Peer's hostname", + "example" : "stage-host-1" + } + } + }, + "PeerRequest" : { + "required" : [ "login_expiration_enabled", "name", "ssh_enabled" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "example" : "stage-host-1" + }, + "ssh_enabled" : { + "type" : "boolean", + "example" : true + }, + "login_expiration_enabled" : { + "type" : "boolean", + "example" : false + } + } + }, + "Peer" : { + "allOf" : [ { + "$ref" : "#/components/schemas/PeerMinimum" + }, { + "required" : [ "connected", "dns_label", "groups", "hostname", "ip", "last_login", "last_seen", "login_expiration_enabled", "login_expired", "os", "ssh_enabled", "version" ], + "type" : "object", + "properties" : { + "ip" : { + "type" : "string", + "description" : "Peer's IP address", + "example" : "10.64.0.1" + }, + "connected" : { + "type" : "boolean", + "description" : "Peer to Management connection status", + "example" : true + }, + "last_seen" : { + "type" : "string", + "description" : "Last time peer connected to Netbird's management service", + "format" : "date-time", + "example" : "2023-05-05T10:05:26.420578Z" + }, + "os" : { + "type" : "string", + "description" : "Peer's operating system and version", + "example" : "Darwin 13.2.1" + }, + "version" : { + "type" : "string", + "description" : "Peer's daemon or cli version", + "example" : "0.14.0" + }, + "groups" : { + "type" : "array", + "description" : "Groups that the peer belongs to", + "items" : { + "$ref" : "#/components/schemas/GroupMinimum" + } + }, + "ssh_enabled" : { + "type" : "boolean", + "description" : "Indicates whether SSH server is enabled on this peer", + "example" : true + }, + "user_id" : { + "type" : "string", + "description" : "User ID of the user that enrolled this peer", + "example" : "google-oauth2|277474792786460067937" + }, + "hostname" : { + "type" : "string", + "description" : "Hostname of the machine", + "example" : "stage-host-1" + }, + "ui_version" : { + "type" : "string", + "description" : "Peer's desktop UI version", + "example" : "0.14.0" + }, + "dns_label" : { + "type" : "string", + "description" : "Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud", + "example" : "stage-host-1.netbird.cloud" + }, + "login_expiration_enabled" : { + "type" : "boolean", + "description" : "Indicates whether peer login expiration has been enabled or not", + "example" : false + }, + "login_expired" : { + "type" : "boolean", + "description" : "Indicates whether peer's login expired or not", + "example" : false + }, + "last_login" : { + "type" : "string", + "description" : "Last time this peer performed log in (authentication). E.g., user authenticated.", + "format" : "date-time", + "example" : "2023-05-05T09:00:35.477782Z" + } + } + } ] + }, + "SetupKey" : { + "required" : [ "auto_groups", "expires", "id", "key", "last_used", "name", "revoked", "state", "type", "updated_at", "usage_limit", "used_times", "valid" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Setup Key ID", + "example" : "2531583362" + }, + "key" : { + "type" : "string", + "description" : "Setup Key value", + "example" : "A616097E-FCF0-48FA-9354-CA4A61142761" + }, + "name" : { + "type" : "string", + "description" : "Setup key name identifier", + "example" : "Default key" + }, + "expires" : { + "type" : "string", + "description" : "Setup Key expiration date", + "format" : "date-time", + "example" : "2023-06-01T14:47:22.291057Z" + }, + "type" : { + "type" : "string", + "description" : "Setup key type, one-off for single time usage and reusable", + "example" : "reusable" + }, + "valid" : { + "type" : "boolean", + "description" : "Setup key validity status", + "example" : true + }, + "revoked" : { + "type" : "boolean", + "description" : "Setup key revocation status", + "example" : false + }, + "used_times" : { + "type" : "integer", + "description" : "Usage count of setup key", + "example" : 2 + }, + "last_used" : { + "type" : "string", + "description" : "Setup key last usage date", + "format" : "date-time", + "example" : "2023-05-05T09:00:35.477782Z" + }, + "state" : { + "type" : "string", + "description" : "Setup key status, \"valid\", \"overused\",\"expired\" or \"revoked\"", + "example" : "valid" + }, + "auto_groups" : { + "type" : "array", + "description" : "Setup key groups to auto-assign to peers registered with this key", + "items" : { + "type" : "string", + "example" : "devs" + } + }, + "updated_at" : { + "type" : "string", + "description" : "Setup key last update date", + "format" : "date-time", + "example" : "2023-05-05T09:00:35.477782Z" + }, + "usage_limit" : { + "type" : "integer", + "description" : "A number of times this key can be used. The value of 0 indicates the unlimited usage.", + "example" : 0 + } + } + }, + "SetupKeyRequest" : { + "required" : [ "auto_groups", "expires_in", "name", "revoked", "type", "usage_limit" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "Setup Key name", + "example" : "Default key" + }, + "type" : { + "type" : "string", + "description" : "Setup key type, one-off for single time usage and reusable", + "example" : "reusable" + }, + "expires_in" : { + "type" : "integer", + "description" : "Expiration time in seconds", + "example" : 43200 + }, + "revoked" : { + "type" : "boolean", + "description" : "Setup key revocation status", + "example" : false + }, + "auto_groups" : { + "type" : "array", + "description" : "Setup key groups to auto-assign to peers registered with this key", + "items" : { + "type" : "string", + "example" : "devs" + } + }, + "usage_limit" : { + "type" : "integer", + "description" : "A number of times this key can be used. The value of 0 indicates the unlimited usage.", + "example" : 0 + } + } + }, + "PersonalAccessToken" : { + "required" : [ "created_at", "created_by", "expiration_date", "id", "name" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "ID of a token", + "example" : "ch8i54g6lnn4g9hqv7n0" + }, + "name" : { + "type" : "string", + "description" : "Name of the token", + "example" : "My first token" + }, + "expiration_date" : { + "type" : "string", + "description" : "Date the token expires", + "format" : "date-time", + "example" : "2023-05-05T14:38:28.977616Z" + }, + "created_by" : { + "type" : "string", + "description" : "User ID of the user who created the token", + "example" : "google-oauth2|277474792786460067937" + }, + "created_at" : { + "type" : "string", + "description" : "Date the token was created", + "format" : "date-time", + "example" : "2023-05-02T14:48:20.465209Z" + }, + "last_used" : { + "type" : "string", + "description" : "Date the token was last used", + "format" : "date-time", + "example" : "2023-05-04T12:45:25.9723616Z" + } + } + }, + "PersonalAccessTokenGenerated" : { + "required" : [ "personal_access_token", "plain_token" ], + "type" : "object", + "properties" : { + "plain_token" : { + "type" : "string", + "description" : "Plain text representation of the generated token", + "example" : "2023-05-02T14:48:20.465209Z" + }, + "personal_access_token" : { + "$ref" : "#/components/schemas/PersonalAccessToken" + } + } + }, + "PersonalAccessTokenRequest" : { + "required" : [ "expires_in", "name" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "Name of the token", + "example" : "My first token" + }, + "expires_in" : { + "maximum" : 365, + "minimum" : 1, + "type" : "integer", + "description" : "Expiration in days", + "example" : 30 + } + } + }, + "GroupMinimum" : { + "required" : [ "id", "name", "peers_count" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Group ID", + "example" : "ch8i4ug6lnn4g9hqv7m0" + }, + "name" : { + "type" : "string", + "description" : "Group Name identifier", + "example" : "devs" + }, + "peers_count" : { + "type" : "integer", + "description" : "Count of peers associated to the group", + "example" : 2 + } + } + }, + "GroupRequest" : { + "required" : [ "name" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "Group name identifier", + "example" : "devs" + }, + "peers" : { + "type" : "array", + "description" : "List of peers ids", + "example" : "ch8i4ug6lnn4g9hqv7m1", + "items" : { + "type" : "string" + } + } + } + }, + "Group" : { + "allOf" : [ { + "$ref" : "#/components/schemas/GroupMinimum" + }, { + "required" : [ "peers" ], + "type" : "object", + "properties" : { + "peers" : { + "type" : "array", + "description" : "List of peers object", + "items" : { + "$ref" : "#/components/schemas/PeerMinimum" + } + } + } + } ] + }, + "RuleMinimum" : { + "required" : [ "description", "disabled", "flow", "name" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "Rule name identifier", + "example" : "Default" + }, + "description" : { + "type" : "string", + "description" : "Rule friendly description", + "example" : "This is a default rule that allows connections between all the resources" + }, + "disabled" : { + "type" : "boolean", + "description" : "Rules status", + "example" : false + }, + "flow" : { + "type" : "string", + "description" : "Rule flow, currently, only \"bidirect\" for bi-directional traffic is accepted", + "example" : "bidirect" + } + } + }, + "RuleRequest" : { + "allOf" : [ { + "$ref" : "#/components/schemas/RuleMinimum" + }, { + "type" : "object", + "properties" : { + "sources" : { + "type" : "array", + "description" : "List of source groups", + "items" : { + "type" : "string", + "example" : "ch8i4ug6lnn4g9hqv7m1" + } + }, + "destinations" : { + "type" : "array", + "description" : "List of destination groups", + "items" : { + "type" : "string", + "example" : "ch8i4ug6lnn4g9hqv7m0" + } + } + } + } ] + }, + "Rule" : { + "allOf" : [ { + "required" : [ "id" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Rule ID", + "example" : "ch8i4ug6lnn4g9hqv7mg" + } + } + }, { + "$ref" : "#/components/schemas/RuleMinimum" + }, { + "required" : [ "destinations", "sources" ], + "type" : "object", + "properties" : { + "sources" : { + "type" : "array", + "description" : "Rule source groups", + "items" : { + "$ref" : "#/components/schemas/GroupMinimum" + } + }, + "destinations" : { + "type" : "array", + "description" : "Rule destination groups", + "items" : { + "$ref" : "#/components/schemas/GroupMinimum" + } + } + } + } ] + }, + "PolicyRule" : { + "required" : [ "action", "destinations", "enabled", "name", "sources" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Rule ID", + "example" : "ch8i4ug6lnn4g9hqv7mg" + }, + "name" : { + "type" : "string", + "description" : "Rule name identifier", + "example" : "Default" + }, + "description" : { + "type" : "string", + "description" : "Rule friendly description", + "example" : "This is a default rule that allows connections between all the resources" + }, + "enabled" : { + "type" : "boolean", + "description" : "Rules status", + "example" : true + }, + "sources" : { + "type" : "array", + "description" : "policy source groups", + "items" : { + "$ref" : "#/components/schemas/GroupMinimum" + } + }, + "destinations" : { + "type" : "array", + "description" : "policy destination groups", + "items" : { + "$ref" : "#/components/schemas/GroupMinimum" + } + }, + "action" : { + "type" : "string", + "description" : "policy accept or drops packets", + "example" : "accept", + "enum" : [ "accept", "drop" ] + } + } + }, + "PolicyMinimum" : { + "required" : [ "description", "enabled", "name", "query", "rules" ], + "type" : "object", + "properties" : { + "name" : { + "type" : "string", + "description" : "Policy name identifier", + "example" : "ch8i4ug6lnn4g9hqv7mg" + }, + "description" : { + "type" : "string", + "description" : "Policy friendly description", + "example" : "This is a default policy that allows connections between all the resources" + }, + "enabled" : { + "type" : "boolean", + "description" : "Policy status", + "example" : true + }, + "query" : { + "type" : "string", + "description" : "Policy Rego query", + "example" : "package netbird\\n\\nall[rule] {\\n is_peer_in_any_group([\\\"ch8i4ug6lnn4g9hqv7m0\\\",\\\"ch8i4ug6lnn4g9hqv7m0\\\"])\\n rule := {\\n rules_from_group(\\\"ch8i4ug6lnn4g9hqv7m0\\\", \\\"dst\\\", \\\"accept\\\", \\\"\\\"),\\n rules_from_group(\\\"ch8i4ug6lnn4g9hqv7m0\\\", \\\"src\\\", \\\"accept\\\", \\\"\\\"),\\n }[_][_]\\n}\\n" + }, + "rules" : { + "type" : "array", + "description" : "Policy rule object for policy UI editor", + "items" : { + "$ref" : "#/components/schemas/PolicyRule" + } + } + } + }, + "Policy" : { + "allOf" : [ { + "$ref" : "#/components/schemas/PolicyMinimum" + }, { + "required" : [ "id" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Policy ID", + "example" : "ch8i4ug6lnn4g9hqv7mg" + } + } + } ] + }, + "RouteRequest" : { + "required" : [ "description", "enabled", "groups", "id", "masquerade", "metric", "network", "network_id", "peer" ], + "type" : "object", + "properties" : { + "description" : { + "type" : "string", + "description" : "Route description", + "example" : "My first route" + }, + "network_id" : { + "maxLength" : 40, + "minLength" : 1, + "type" : "string", + "description" : "Route network identifier, to group HA routes", + "example" : "Route 1" + }, + "enabled" : { + "type" : "boolean", + "description" : "Route status", + "example" : true + }, + "peer" : { + "type" : "string", + "description" : "Peer Identifier associated with route", + "example" : "chacbco6lnnbn6cg5s91" + }, + "network" : { + "type" : "string", + "description" : "Network range in CIDR format", + "example" : "10.64.0.0/24" + }, + "metric" : { + "maximum" : 9999, + "minimum" : 1, + "type" : "integer", + "description" : "Route metric number. Lowest number has higher priority", + "example" : 9999 + }, + "masquerade" : { + "type" : "boolean", + "description" : "Indicate if peer should masquerade traffic to this route's prefix", + "example" : true + }, + "groups" : { + "type" : "array", + "description" : "Route group tag groups", + "items" : { + "type" : "string", + "example" : "chacdk86lnnboviihd70" + } + } + } + }, + "Route" : { + "allOf" : [ { + "required" : [ "id", "network_type" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Route Id", + "example" : "chacdk86lnnboviihd7g" + }, + "network_type" : { + "type" : "string", + "description" : "Network type indicating if it is IPv4 or IPv6", + "example" : "IPv4" + } + } + }, { + "$ref" : "#/components/schemas/RouteRequest" + } ] + }, + "Nameserver" : { + "required" : [ "ip", "ns_type", "port" ], + "type" : "object", + "properties" : { + "ip" : { + "type" : "string", + "description" : "Nameserver IP", + "example" : "8.8.8.8" + }, + "ns_type" : { + "type" : "string", + "description" : "Nameserver Type", + "example" : "udp", + "enum" : [ "udp" ] + }, + "port" : { + "type" : "integer", + "description" : "Nameserver Port", + "example" : 53 + } + } + }, + "NameserverGroupRequest" : { + "required" : [ "description", "domains", "enabled", "groups", "name", "nameservers", "primary" ], + "type" : "object", + "properties" : { + "name" : { + "maxLength" : 40, + "minLength" : 1, + "type" : "string", + "description" : "Nameserver group name", + "example" : "Google DNS" + }, + "description" : { + "type" : "string", + "description" : "Nameserver group description", + "example" : "Google DNS servers" + }, + "nameservers" : { + "maxLength" : 2, + "minLength" : 1, + "type" : "array", + "description" : "Nameserver group", + "items" : { + "$ref" : "#/components/schemas/Nameserver" + } + }, + "enabled" : { + "type" : "boolean", + "description" : "Nameserver group status", + "example" : true + }, + "groups" : { + "type" : "array", + "description" : "Nameserver group tag groups", + "items" : { + "type" : "string", + "example" : "ch8i4ug6lnn4g9hqv7m0" + } + }, + "primary" : { + "type" : "boolean", + "description" : "Nameserver group primary status", + "example" : true + }, + "domains" : { + "type" : "array", + "description" : "Nameserver group domain list", + "items" : { + "maxLength" : 255, + "minLength" : 1, + "type" : "string", + "example" : "example.com" + } + } + } + }, + "NameserverGroup" : { + "allOf" : [ { + "required" : [ "id" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Nameserver group ID", + "example" : "ch8i4ug6lnn4g9hqv7m0" + } + } + }, { + "$ref" : "#/components/schemas/NameserverGroupRequest" + } ] + }, + "DNSSettings" : { + "required" : [ "disabled_management_groups" ], + "type" : "object", + "properties" : { + "disabled_management_groups" : { + "type" : "array", + "description" : "Groups whose DNS management is disabled", + "items" : { + "type" : "string", + "example" : "ch8i4ug6lnn4g9hqv7m0" + } + } + } + }, + "Event" : { + "required" : [ "activity", "activity_code", "id", "initiator_id", "meta", "target_id", "timestamp" ], + "type" : "object", + "properties" : { + "id" : { + "type" : "string", + "description" : "Event unique identifier", + "example" : "10" + }, + "timestamp" : { + "type" : "string", + "description" : "The date and time when the event occurred", + "format" : "date-time", + "example" : "2023-05-05T10:04:37.473542Z" + }, + "activity" : { + "type" : "string", + "description" : "The activity that occurred during the event", + "example" : "Route created" + }, + "activity_code" : { + "type" : "string", + "description" : "The string code of the activity that occurred during the event", + "example" : "route.add", + "enum" : [ "user.peer.delete", "user.join", "user.invite", "user.peer.add", "user.group.add", "user.group.delete", "user.role.update", "setupkey.peer.add", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse", "setupkey.group.delete", "setupkey.group.add", "rule.add", "rule.delete", "rule.update", "policy.add", "policy.delete", "policy.update", "group.add", "group.update", "dns.setting.disabled.management.group.add", "dns.setting.disabled.management.group.delete", "account.create", "account.setting.peer.login.expiration.update", "account.setting.peer.login.expiration.disable", "account.setting.peer.login.expiration.enable", "route.add", "route.delete", "route.update", "nameserver.group.add", "nameserver.group.delete", "nameserver.group.update", "peer.ssh.disable", "peer.ssh.enable", "peer.rename", "peer.login.expiration.disable", "peer.login.expiration.enable", "service.user.create", "personal.access.token.create", "service.user.delete", "personal.access.token.delete" ] + }, + "initiator_id" : { + "type" : "string", + "description" : "The ID of the initiator of the event. E.g., an ID of a user that triggered the event.", + "example" : "google-oauth2|123456789012345678901" + }, + "target_id" : { + "type" : "string", + "description" : "The ID of the target of the event. E.g., an ID of the peer that a user removed.", + "example" : "chad9d86lnnc59g18ou0" + }, + "meta" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + }, + "description" : "The metadata of the event", + "example" : { + "name" : "my route", + "network_range" : "10.64.0.0/24", + "peer_id" : "chacbco6lnnbn6cg5s91" + } + } + } + } + }, + "responses" : { + "not_found" : { + "description" : "Resource not found", + "content" : { } + }, + "validation_failed_simple" : { + "description" : "Validation failed", + "content" : { } + }, + "bad_request" : { + "description" : "Bad Request", + "content" : { } + }, + "internal_error" : { + "description" : "Internal Server Error", + "content" : { } + }, + "validation_failed" : { + "description" : "Validation failed", + "content" : { } + }, + "forbidden" : { + "description" : "Forbidden", + "content" : { } + }, + "requires_authentication" : { + "description" : "Requires authentication", + "content" : { } + } + }, + "securitySchemes" : { + "BearerAuth" : { + "type" : "http", + "scheme" : "bearer", + "bearerFormat" : "JWT" + }, + "TokenAuth" : { + "type" : "apiKey", + "description" : "Enter the token with the `Token` prefix, e.g. \"Token nbp_F3f0d.....\".", + "name" : "Authorization", + "in" : "header" + } + } + } +} \ No newline at end of file diff --git a/generator/api.ts b/generator/api.ts new file mode 100644 index 00000000..be74f984 --- /dev/null +++ b/generator/api.ts @@ -0,0 +1,233 @@ +import template from './templates/ApiTemplate' +import { slugify, toArrayWithKey, toTitle, writeToDisk } from './helpers' +import { OpenAPIV3, OpenAPIV2 } from 'openapi-types' +import * as fs from 'fs' +import * as ejs from 'ejs' + +export default async function gen(inputFileName: string, outputDir: string) { + const specRaw = fs.readFileSync(inputFileName, 'utf8') + const spec = JSON.parse(specRaw) as any + // console.log('spec', spec) + + switch (spec.openapi || spec.swagger) { + case '3.0.0': + case '3.0.1': + case '3.0.3': + await gen_v3(spec, outputDir) + break + + default: + console.log('Unrecognized specification version:', spec.openapi) + break + } +} + +/** + * Versioned Generator + */ + +// OPENAPI-SPEC-VERSION: 3.0.0 +type v3OperationWithPath = OpenAPIV3.OperationObject & { + path: string +} +export type enrichedOperation = OpenAPIV3.OperationObject & { + path: string + fullPath: string + operationId: string +} + + +export type schemaParameter = { + name: string + type: string + description: string + required: boolean + minimum?: number + maximum?: number + minLength?: number + maxLength?: number + enum?: string[] +} + +export type component = { + example: Object + schema: Object + parameters: schemaParameter[] +} + +async function gen_v3(spec: OpenAPIV3.Document, dest: string) { + const specLayout = spec.tags || [] + // const operations: enrichedOperation[] = [] + const tagGroups = new Map() + const server = spec.servers.pop().url + Object.entries(spec.paths).forEach(([key, val]) => { + const fullPath = `${server}${key}` + + toArrayWithKey(val!, 'operation').forEach((o) => { + const operation = o as v3OperationWithPath + const enriched = { + ...operation, + path: key, + fullPath, + operationId: slugify(operation.summary!), + + responseList: toArrayWithKey(operation.responses!, 'responseCode') || [], + } + let tag = operation.tags.pop() + let tagOperations = tagGroups.get(tag) ?? [] + tagGroups.set(tag, tagOperations.concat(enriched)) + }) + }) + + let components = new Map(); + Object.entries(spec.components?.schemas).forEach(([key, val]) => { + const schema = val as OpenAPIV3.SchemaObject + let outputSchema = new Map(); + let outputExample = new Map(); + let parameters : schemaParameter[] = [] + if(schema.allOf){ + schema.allOf.forEach((item) => { + if((item as OpenAPIV3.ReferenceObject).$ref){ + let component = components.get((item as OpenAPIV3.ReferenceObject).$ref.split('/').pop()) + let schemaMap = new Map(Object.entries(component.schema)) + let exampleMap = new Map(Object.entries(component.example)) + schemaMap.forEach((value, key) => { + outputSchema.set(key, value) + }) + exampleMap.forEach((value, key) => { + outputExample.set(key, value) + }) + parameters = parameters.concat(component.parameters) + } + if((item as OpenAPIV3.SchemaObject).properties){ + Object.entries((item as OpenAPIV3.SchemaObject).properties).forEach(([key, val]) => { + let property = val as OpenAPIV3.SchemaObject + let type, exampleValue + if (property.type === "array") { + type = new Array(resolveType(property.items, spec.components?.schemas)) + exampleValue = new Array(resolveExampleValue(property.items, spec.components?.schemas)) + } else { + type = resolveType(property, spec.components?.schemas) + exampleValue = resolveExampleValue(property, spec.components?.schemas) + } + outputSchema.set(key, type) + outputExample.set(key, exampleValue) + let parameter: schemaParameter = { + name: key, + type: property.type === "array" ? ((property.items as OpenAPIV3.SchemaObject).type || (property.items as OpenAPIV3.ReferenceObject).$ref.split('/').pop()) + "[]" : property.type, + description: property.description, + required: schema.required?.includes(key) || false, + minimum: property.minimum, + maximum: property.maximum, + minLength: property.minLength, + maxLength: property.maxLength, + enum: property.enum + } + parameters.push(parameter) + }) + } + }) + } else { + Object.entries(schema.properties).forEach(([key, val]) => { + let property = val as OpenAPIV3.SchemaObject + let type, exampleValue + if(property.type === "array"){ + type = new Array(resolveType(property.items, spec.components?.schemas)) + exampleValue = new Array(resolveExampleValue(property.items, spec.components?.schemas)) + } else { + type = resolveType(property, spec.components?.schemas) + exampleValue = resolveExampleValue(property, spec.components?.schemas) + } + outputSchema.set(key, type) + outputExample.set(key, exampleValue) + let parameter : schemaParameter = { + name: key, + type: property.type === "array" ? ((property.items as OpenAPIV3.SchemaObject).type || (property.items as OpenAPIV3.ReferenceObject).$ref.split('/').pop()) + "[]" : property.type, + description: property.description, + required: schema.required?.includes(key) || false, + minimum: property.minimum, + maximum: property.maximum, + minLength: property.minLength, + maxLength: property.maxLength, + enum: property.enum + } + parameters.push(parameter) + }) + } + + let output : component = { + example: Object.fromEntries(outputExample), + schema: Object.fromEntries(outputSchema), + parameters: parameters + } + components.set(key, output) + }) + + + tagGroups.forEach((value: enrichedOperation[], key: string) => { + + const operations = value + + const sections = specLayout.map((section) => { + return { + ...section, + title: toTitle(section.name), + id: slugify(section.name), + operations: operations, + } + }) + + const content = ejs.render(template, { + info: spec.info, + tag: key, + sections, + operations, + components, + }) + + // Write to disk + let outputFile = dest + "/" + key.toLowerCase().replace(" ", "-") + ".mdx" + writeToDisk(outputFile, content) + // console.log('Saved: ', outputFile) + }) +} + +function resolveType(items: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | OpenAPIV3.NonArraySchemaObjectType, schemas) : any { + if((items as OpenAPIV3.ReferenceObject).$ref){ + let ref = (items as OpenAPIV3.ReferenceObject).$ref + let map = new Map() + Object.entries(schemas[ref.split('/').pop()].properties).forEach(([key, val]) => { + let property = val as OpenAPIV3.SchemaObject + let type + if(property.type === "array"){ + type = new Array(resolveType(property.items, schemas)) + } else { + type = resolveType(property, schemas) + } + map.set(key, type) + }) + return Object.fromEntries(map) + } + return (items as OpenAPIV3.ArraySchemaObject).type +} + + +function resolveExampleValue(items: OpenAPIV3.ReferenceObject | OpenAPIV3.SchemaObject | OpenAPIV3.NonArraySchemaObjectType, schemas) : any { + if((items as OpenAPIV3.ReferenceObject).$ref){ + let ref = (items as OpenAPIV3.ReferenceObject).$ref + let map = new Map() + Object.entries(schemas[ref.split('/').pop()].properties).forEach(([key, val]) => { + let property = val as OpenAPIV3.SchemaObject + let exampleValue + if(property.type === "array"){ + exampleValue = new Array(resolveExampleValue(property.items, schemas)) + } else { + exampleValue = resolveExampleValue(property, schemas) + } + map.set(key, exampleValue) + }) + return Object.fromEntries(map) + } + return (items as OpenAPIV3.ArraySchemaObject).example +} + diff --git a/generator/helpers.ts b/generator/helpers.ts new file mode 100644 index 00000000..e7796c91 --- /dev/null +++ b/generator/helpers.ts @@ -0,0 +1,42 @@ +import * as _ from 'lodash' +import * as fs from 'fs' + +export const slugify = (text: string) => { + if (!text) return '' + return text + .toString() + .toLowerCase() + .replace(/[. )(]/g, '-') // Replace spaces and brackets - + .replace(/[^\w\-]+/g, '') // Remove all non-word chars + .replace(/\-\-+/g, '-') // Replace multiple - with single - + .replace(/^-+/, '') // Trim - from start of text + .replace(/-+$/, '') // Trim - from end of text +} + +// Uppercase the first letter of a string +export const toTitle = (text: string) => { + return text.charAt(0).toUpperCase() + text.slice(1) +} + +/** + * writeToDisk() + */ +export const writeToDisk = (fileName: string, content: any) => { + return new Promise((resolve, reject) => { + fs.writeFile(fileName, content, (err: any) => { + if (err) return reject(err) + else return resolve(true) + }) + }) +} + +/** + * Convert Object to Array of values + */ +export const toArrayWithKey = (obj: object, keyAs: string) => + _.values( + _.mapValues(obj, (value: any, key: string) => { + value[keyAs] = key + return value + }) + ) diff --git a/generator/index.ts b/generator/index.ts new file mode 100644 index 00000000..e215d25c --- /dev/null +++ b/generator/index.ts @@ -0,0 +1,33 @@ +import ApiGenerator from './api' + + +const main = (command: string[], options: any) => { + handleInput(command[0], options) +} + +// Run everything +const argv = require('minimist')(process.argv.slice(2)) +main(argv['_'], argv) + +function handleInput(command: string, options: any) { + switch (command) { + case 'gen': + DocGenerator(options) + break + + default: + console.log('Unrecognized command:', command) + break + } +} + +export default async function DocGenerator({ + input, + output, +}: { + input: string + output: string +}) { + await ApiGenerator(input, output) + return 'Done' +} diff --git a/generator/openapi.yml b/generator/openapi.yml new file mode 100644 index 00000000..8d3b7a5b --- /dev/null +++ b/generator/openapi.yml @@ -0,0 +1,2219 @@ +openapi: 3.0.1 +servers: + - url: https://api.netbird.io + description: Default server +info: + title: NetBird REST API + description: API to manipulate groups, rules, policies and retrieve information about peers and users + version: 0.0.1 +tags: + - name: Users + description: Interact with and view information about users. + - name: Tokens + description: Interact with and view information about tokens. + - name: Peers + description: Interact with and view information about peers. + - name: Setup Keys + description: Interact with and view information about setup keys. + - name: Groups + description: Interact with and view information about groups. + - name: Rules + description: Interact with and view information about rules. + - name: Policies + description: Interact with and view information about policies. + - name: Routes + description: Interact with and view information about routes. + - name: DNS + description: Interact with and view information about DNS configuration. + - name: Events + description: View information about the account and network events. + - name: Accounts + description: View information about the accounts. +components: + schemas: + Account: + properties: + id: + description: Account ID + type: string + example: ch8i4ug6lnn4g9hqv7l0 + settings: + $ref: '#/components/schemas/AccountSettings' + required: + - id + - settings + AccountSettings: + properties: + peer_login_expiration_enabled: + description: 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). + type: boolean + example: true + peer_login_expiration: + description: Period of time after which peer login expires (seconds). + type: integer + example: 43200 + required: + - peer_login_expiration_enabled + - peer_login_expiration + User: + type: object + properties: + id: + description: User ID + type: string + example: google-oauth2|277474792786460067937 + email: + description: User's email address + type: string + example: demo@netbird.io + name: + description: User's name from idp provider + type: string + example: Tom Schulz + role: + description: User's NetBird account role + type: string + example: admin + status: + description: User's status + type: string + enum: [ "active","invited","disabled" ] + example: active + auto_groups: + description: Groups to auto-assign to peers registered by this user + type: array + items: + type: string + example: devs + is_current: + description: Is true if authenticated user is the same as this user + type: boolean + readOnly: true + example: true + is_service_user: + description: Is true if this user is a service user + type: boolean + readOnly: true + example: false + required: + - id + - email + - name + - role + - auto_groups + - status + UserRequest: + type: object + properties: + role: + description: User's NetBird account role + type: string + example: admin + auto_groups: + description: Groups to auto-assign to peers registered by this user + type: array + items: + type: string + example: devs + required: + - role + - auto_groups + UserCreateRequest: + type: object + properties: + email: + description: User's Email to send invite to + type: string + example: demo@netbird.io + name: + description: User's full name + type: string + example: Tom Schulz + role: + description: User's NetBird account role + type: string + example: admin + auto_groups: + description: Groups to auto-assign to peers registered by this user + type: array + items: + type: string + example: devs + is_service_user: + description: Is true if this user is a service user + type: boolean + example: false + required: + - role + - auto_groups + - is_service_user + PeerMinimum: + type: object + properties: + id: + description: Peer ID + type: string + example: chacbco6lnnbn6cg5s90 + name: + description: Peer's hostname + type: string + example: stage-host-1 + required: + - id + - name + PeerRequest: + type: object + properties: + name: + type: string + example: stage-host-1 + ssh_enabled: + type: boolean + example: true + login_expiration_enabled: + type: boolean + example: false + required: + - name + - ssh_enabled + - login_expiration_enabled + Peer: + allOf: + - $ref: '#/components/schemas/PeerMinimum' + - type: object + properties: + ip: + description: Peer's IP address + type: string + example: 10.64.0.1 + connected: + description: Peer to Management connection status + type: boolean + example: true + last_seen: + description: Last time peer connected to Netbird's management service + type: string + format: date-time + example: 2023-05-05T10:05:26.420578Z + os: + description: Peer's operating system and version + type: string + example: Darwin 13.2.1 + version: + description: Peer's daemon or cli version + type: string + example: 0.14.0 + groups: + description: Groups that the peer belongs to + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + ssh_enabled: + description: Indicates whether SSH server is enabled on this peer + type: boolean + example: true + user_id: + description: User ID of the user that enrolled this peer + type: string + example: google-oauth2|277474792786460067937 + hostname: + description: Hostname of the machine + type: string + example: stage-host-1 + ui_version: + description: Peer's desktop UI version + type: string + example: 0.14.0 + dns_label: + description: Peer's DNS label is the parsed peer name for domain resolution. It is used to form an FQDN by appending the account's domain to the peer label. e.g. peer-dns-label.netbird.cloud + type: string + example: stage-host-1.netbird.cloud + login_expiration_enabled: + description: Indicates whether peer login expiration has been enabled or not + type: boolean + example: false + login_expired: + description: Indicates whether peer's login expired or not + type: boolean + example: false + last_login: + description: Last time this peer performed log in (authentication). E.g., user authenticated. + type: string + format: date-time + example: 2023-05-05T09:00:35.477782Z + required: + - ip + - connected + - last_seen + - os + - version + - groups + - ssh_enabled + - hostname + - dns_label + - login_expiration_enabled + - login_expired + - last_login + SetupKey: + type: object + properties: + id: + description: Setup Key ID + type: string + example: 2531583362 + key: + description: Setup Key value + type: string + example: A616097E-FCF0-48FA-9354-CA4A61142761 + name: + description: Setup key name identifier + type: string + example: Default key + expires: + description: Setup Key expiration date + type: string + format: date-time + example: 2023-06-01T14:47:22.291057Z + type: + description: Setup key type, one-off for single time usage and reusable + type: string + example: reusable + valid: + description: Setup key validity status + type: boolean + example: true + revoked: + description: Setup key revocation status + type: boolean + example: false + used_times: + description: Usage count of setup key + type: integer + example: 2 + last_used: + description: Setup key last usage date + type: string + format: date-time + example: 2023-05-05T09:00:35.477782Z + state: + description: Setup key status, "valid", "overused","expired" or "revoked" + type: string + example: valid + auto_groups: + description: Setup key groups to auto-assign to peers registered with this key + type: array + items: + type: string + example: "devs" + updated_at: + description: Setup key last update date + type: string + format: date-time + example: 2023-05-05T09:00:35.477782Z + usage_limit: + description: A number of times this key can be used. The value of 0 indicates the unlimited usage. + type: integer + example: 0 + required: + - id + - key + - name + - expires + - type + - valid + - revoked + - used_times + - last_used + - state + - auto_groups + - updated_at + - usage_limit + SetupKeyRequest: + type: object + properties: + name: + description: Setup Key name + type: string + example: Default key + type: + description: Setup key type, one-off for single time usage and reusable + type: string + example: reusable + expires_in: + description: Expiration time in seconds + type: integer + example: 43200 + revoked: + description: Setup key revocation status + type: boolean + example: false + auto_groups: + description: Setup key groups to auto-assign to peers registered with this key + type: array + items: + type: string + example: "devs" + usage_limit: + description: A number of times this key can be used. The value of 0 indicates the unlimited usage. + type: integer + example: 0 + required: + - name + - type + - expires_in + - revoked + - auto_groups + - usage_limit + PersonalAccessToken: + type: object + properties: + id: + description: ID of a token + type: string + example: ch8i54g6lnn4g9hqv7n0 + name: + description: Name of the token + type: string + example: My first token + expiration_date: + description: Date the token expires + type: string + format: date-time + example: 2023-05-05T14:38:28.977616Z + created_by: + description: User ID of the user who created the token + type: string + example: google-oauth2|277474792786460067937 + created_at: + description: Date the token was created + type: string + format: date-time + example: 2023-05-02T14:48:20.465209Z + last_used: + description: Date the token was last used + type: string + format: date-time + example: 2023-05-04T12:45:25.9723616Z + required: + - id + - name + - expiration_date + - created_by + - created_at + PersonalAccessTokenGenerated: + type: object + properties: + plain_token: + description: Plain text representation of the generated token + type: string + example: 2023-05-02T14:48:20.465209Z + personal_access_token: + $ref: '#/components/schemas/PersonalAccessToken' + required: + - plain_token + - personal_access_token + PersonalAccessTokenRequest: + type: object + properties: + name: + description: Name of the token + type: string + example: My first token + expires_in: + description: Expiration in days + type: integer + minimum: 1 + maximum: 365 + example: 30 + required: + - name + - expires_in + GroupMinimum: + type: object + properties: + id: + description: Group ID + type: string + example: ch8i4ug6lnn4g9hqv7m0 + name: + description: Group Name identifier + type: string + example: devs + peers_count: + description: Count of peers associated to the group + type: integer + example: 2 + required: + - id + - name + - peers_count + GroupRequest: + type: object + properties: + name: + type: string + description: Group name identifier + example: devs + peers: + type: array + description: List of peers ids + items: + type: string + example: "ch8i4ug6lnn4g9hqv7m1" + required: + - name + Group: + allOf: + - $ref: '#/components/schemas/GroupMinimum' + - type: object + properties: + peers: + description: List of peers object + type: array + items: + $ref: '#/components/schemas/PeerMinimum' + required: + - peers + RuleMinimum: + type: object + properties: + name: + description: Rule name identifier + type: string + example: Default + description: + description: Rule friendly description + type: string + example: This is a default rule that allows connections between all the resources + disabled: + description: Rules status + type: boolean + example: false + flow: + description: Rule flow, currently, only "bidirect" for bi-directional traffic is accepted + type: string + example: bidirect + required: + - name + - description + - disabled + - flow + RuleRequest: + allOf: + - $ref: '#/components/schemas/RuleMinimum' + - type: object + properties: + sources: + type: array + description: List of source groups + items: + type: string + example: "ch8i4ug6lnn4g9hqv7m1" + destinations: + type: array + description: List of destination groups + items: + type: string + example: "ch8i4ug6lnn4g9hqv7m0" + Rule: + allOf: + - type: object + properties: + id: + description: Rule ID + type: string + example: ch8i4ug6lnn4g9hqv7mg + required: + - id + - $ref: '#/components/schemas/RuleMinimum' + - type: object + properties: + sources: + description: Rule source groups + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + destinations: + description: Rule destination groups + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + required: + - sources + - destinations + PolicyRule: + type: object + properties: + id: + description: Rule ID + type: string + example: ch8i4ug6lnn4g9hqv7mg + name: + description: Rule name identifier + type: string + example: Default + description: + description: Rule friendly description + type: string + example: This is a default rule that allows connections between all the resources + enabled: + description: Rules status + type: boolean + example: true + sources: + description: policy source groups + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + destinations: + description: policy destination groups + type: array + items: + $ref: '#/components/schemas/GroupMinimum' + action: + description: policy accept or drops packets + type: string + enum: ["accept","drop"] + example: accept + required: + - name + - sources + - destinations + - action + - enabled + PolicyMinimum: + type: object + properties: + name: + description: Policy name identifier + type: string + example: ch8i4ug6lnn4g9hqv7mg + description: + description: Policy friendly description + type: string + example: This is a default policy that allows connections between all the resources + enabled: + description: Policy status + type: boolean + example: true + query: + description: Policy Rego query + type: string + example: package netbird\n\nall[rule] {\n is_peer_in_any_group([\"ch8i4ug6lnn4g9hqv7m0\",\"ch8i4ug6lnn4g9hqv7m0\"])\n rule := {\n rules_from_group(\"ch8i4ug6lnn4g9hqv7m0\", \"dst\", \"accept\", \"\"),\n rules_from_group(\"ch8i4ug6lnn4g9hqv7m0\", \"src\", \"accept\", \"\"),\n }[_][_]\n}\n + rules: + description: Policy rule object for policy UI editor + type: array + items: + $ref: '#/components/schemas/PolicyRule' + required: + - name + - description + - enabled + - query + - rules + Policy: + allOf: + - $ref: '#/components/schemas/PolicyMinimum' + - type: object + properties: + id: + description: Policy ID + type: string + example: ch8i4ug6lnn4g9hqv7mg + required: + - id + RouteRequest: + type: object + properties: + description: + description: Route description + type: string + example: My first route + network_id: + description: Route network identifier, to group HA routes + type: string + maxLength: 40 + minLength: 1 + example: Route 1 + enabled: + description: Route status + type: boolean + example: true + peer: + description: Peer Identifier associated with route + type: string + example: chacbco6lnnbn6cg5s91 + network: + description: Network range in CIDR format + type: string + example: 10.64.0.0/24 + metric: + description: Route metric number. Lowest number has higher priority + type: integer + maximum: 9999 + minimum: 1 + example: 9999 + masquerade: + description: Indicate if peer should masquerade traffic to this route's prefix + type: boolean + example: true + groups: + description: Route group tag groups + type: array + items: + type: string + example: "chacdk86lnnboviihd70" + required: + - id + - description + - network_id + - enabled + - peer + - network + - metric + - masquerade + - groups + Route: + allOf: + - type: object + properties: + id: + description: Route Id + type: string + example: chacdk86lnnboviihd7g + network_type: + description: Network type indicating if it is IPv4 or IPv6 + type: string + example: IPv4 + required: + - id + - network_type + - $ref: '#/components/schemas/RouteRequest' + Nameserver: + type: object + properties: + ip: + description: Nameserver IP + type: string + example: 8.8.8.8 + ns_type: + description: Nameserver Type + type: string + enum: [ "udp" ] + example: udp + port: + description: Nameserver Port + type: integer + example: 53 + required: + - ip + - ns_type + - port + NameserverGroupRequest: + type: object + properties: + name: + description: Nameserver group name + type: string + maxLength: 40 + minLength: 1 + example: Google DNS + description: + description: Nameserver group description + type: string + example: Google DNS servers + nameservers: + description: Nameserver group + minLength: 1 + maxLength: 2 + type: array + items: + $ref: '#/components/schemas/Nameserver' + enabled: + description: Nameserver group status + type: boolean + example: true + groups: + description: Nameserver group tag groups + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + primary: + description: Nameserver group primary status + type: boolean + example: true + domains: + description: Nameserver group domain list + type: array + items: + type: string + minLength: 1 + maxLength: 255 + example: "example.com" + required: + - name + - description + - nameservers + - enabled + - groups + - primary + - domains + NameserverGroup: + allOf: + - type: object + properties: + id: + description: Nameserver group ID + type: string + example: ch8i4ug6lnn4g9hqv7m0 + required: + - id + - $ref: '#/components/schemas/NameserverGroupRequest' + DNSSettings: + type: object + properties: + disabled_management_groups: + description: Groups whose DNS management is disabled + type: array + items: + type: string + example: ch8i4ug6lnn4g9hqv7m0 + required: + - disabled_management_groups + Event: + type: object + properties: + id: + description: Event unique identifier + type: string + example: 10 + timestamp: + description: The date and time when the event occurred + type: string + format: date-time + example: 2023-05-05T10:04:37.473542Z + activity: + description: The activity that occurred during the event + type: string + example: Route created + activity_code: + description: The string code of the activity that occurred during the event + type: string + enum: [ "user.peer.delete", "user.join", "user.invite", "user.peer.add", "user.group.add", "user.group.delete", + "user.role.update", + "setupkey.peer.add", "setupkey.add", "setupkey.update", "setupkey.revoke", "setupkey.overuse", + "setupkey.group.delete", "setupkey.group.add", + "rule.add", "rule.delete", "rule.update", + "policy.add", "policy.delete", "policy.update", + "group.add", "group.update", "dns.setting.disabled.management.group.add", "dns.setting.disabled.management.group.delete", + "account.create", "account.setting.peer.login.expiration.update", "account.setting.peer.login.expiration.disable", "account.setting.peer.login.expiration.enable", + "route.add", "route.delete", "route.update", + "nameserver.group.add", "nameserver.group.delete", "nameserver.group.update", + "peer.ssh.disable", "peer.ssh.enable", "peer.rename", "peer.login.expiration.disable", "peer.login.expiration.enable", + "service.user.create", "personal.access.token.create", "service.user.delete", "personal.access.token.delete" ] + example: route.add + initiator_id: + description: The ID of the initiator of the event. E.g., an ID of a user that triggered the event. + type: string + example: google-oauth2|123456789012345678901 + target_id: + description: The ID of the target of the event. E.g., an ID of the peer that a user removed. + type: string + example: chad9d86lnnc59g18ou0 + meta: + description: The metadata of the event + type: object + additionalProperties: + type: string + example: { "name": "my route", "network_range": "10.64.0.0/24", "peer_id": "chacbco6lnnbn6cg5s91"} + required: + - id + - timestamp + - activity + - activity_code + - initiator_id + - target_id + - meta + responses: + not_found: + description: Resource not found + content: { } + validation_failed_simple: + description: Validation failed + content: { } + bad_request: + description: Bad Request + content: { } + internal_error: + description: Internal Server Error + content: { } + validation_failed: + description: Validation failed + content: { } + forbidden: + description: Forbidden + content: { } + requires_authentication: + description: Requires authentication + content: { } + securitySchemes: + BearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + TokenAuth: + type: apiKey + in: header + name: Authorization + description: >- + Enter the token with the `Token` prefix, e.g. "Token nbp_F3f0d.....". +security: + - BearerAuth: [ ] + - TokenAuth: [ ] +paths: + /api/accounts: + get: + summary: List all Accounts + description: Returns a list of accounts of a user. Always returns a list of one account. + tags: [ Accounts ] + operationId: getAccounts + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON array of accounts + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Account' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/accounts/{accountId}: + put: + summary: Update an Account + description: Update information about an account + tags: [ Accounts ] + operationId: updateAccount + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: accountId + required: true + schema: + type: string + description: The unique identifier of an account + requestBody: + description: update an account + content: + 'application/json': + schema: + $ref: '#/components/schemas/AccountSettings' + responses: + '200': + description: An Account object + content: + application/json: + schema: + $ref: '#/components/schemas/Account' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users: + get: + summary: List all Users + description: Returns a list of all users + tags: [ Users ] + operationId: getUsers + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: query + name: service_user + schema: + type: boolean + description: Filters users and returns either regular users or service users + responses: + '200': + description: A JSON array of Users + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a User + description: Creates a new service user or sends an invite to a regular user + tags: [ Users ] + operationId: createUser + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: User invite information + content: + 'application/json': + schema: + $ref: '#/components/schemas/UserCreateRequest' + responses: + '200': + description: A User object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}: + put: + summary: Update a User + description: Update information about a User + tags: [ Users ] + operationId: updateUser + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + requestBody: + description: User update + content: + 'application/json': + schema: + $ref: '#/components/schemas/UserRequest' + responses: + '200': + description: A User object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a User + description: Delete a User + tags: [ Users ] + operationId: deleteUser + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/tokens: + get: + summary: List all Tokens + description: Returns a list of all tokens for a user + tags: [ Tokens ] + operationId: getTokens + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + responses: + '200': + description: A JSON Array of PersonalAccessTokens + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PersonalAccessToken' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Token + description: Create a new token for a user + tags: [ Tokens ] + operationId: createToken + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + requestBody: + description: PersonalAccessToken create parameters + content: + application/json: + schema: + $ref: '#/components/schemas/PersonalAccessTokenRequest' + responses: + '200': + description: The token in plain text + content: + application/json: + schema: + $ref: '#/components/schemas/PersonalAccessTokenGenerated' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/users/{userId}/tokens/{tokenId}: + get: + summary: Retrieve a Token + description: Returns a specific token for a user + tags: [ Tokens ] + operationId: getToken + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + - in: path + name: tokenId + required: true + schema: + type: string + description: The unique identifier of a token + responses: + '200': + description: A PersonalAccessTokens Object + content: + application/json: + schema: + $ref: '#/components/schemas/PersonalAccessToken' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Token + description: Delete a token for a user + tags: [ Tokens ] + operationId: deleteToken + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: userId + required: true + schema: + type: string + description: The unique identifier of a user + - in: path + name: tokenId + required: true + schema: + type: string + description: The unique identifier of a token + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers: + get: + summary: List all Peers + description: Returns a list of all peers + tags: [ Peers ] + operationId: getPeers + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Peers + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Peer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/peers/{peerId}: + get: + summary: Retrieve a Peer + description: Get information about a peer + tags: [ Peers ] + operationId: getPeer + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + responses: + '200': + description: A Peer object + content: + application/json: + schema: + $ref: '#/components/schemas/Peer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Peer + description: Update information about a peer + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + requestBody: + description: update a peer + content: + 'application/json': + schema: + $ref: '#/components/schemas/PeerRequest' + responses: + '200': + description: A Peer object + content: + application/json: + schema: + $ref: '#/components/schemas/Peer' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Peer + description: Delete a peer + tags: [ Peers ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: peerId + required: true + schema: + type: string + description: The unique identifier of a peer + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/setup-keys: + get: + summary: List all Setup Keys + description: Returns a list of all Setup Keys + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Setup keys + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/SetupKey' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Setup Key + description: Creates a Setup Key + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Setup Key request + content: + 'application/json': + schema: + $ref: '#/components/schemas/SetupKeyRequest' + responses: + '200': + description: A Setup Keys Object + content: + application/json: + schema: + $ref: '#/components/schemas/SetupKey' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/setup-keys/{keyId}: + get: + summary: Retrieve a Setup Key + description: Get information about a Setup Key + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: keyId + required: true + schema: + type: string + description: The unique identifier of a setup key + responses: + '200': + description: A Setup Key object + content: + application/json: + schema: + $ref: '#/components/schemas/SetupKey' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Setup Key + description: Update information about a Setup Key + tags: [ Setup Keys ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: keyId + required: true + schema: + type: string + description: The unique identifier of a setup key + requestBody: + description: update to Setup Key + content: + 'application/json': + schema: + $ref: '#/components/schemas/SetupKeyRequest' + responses: + '200': + description: A Setup Key object + content: + application/json: + schema: + $ref: '#/components/schemas/SetupKey' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/groups: + get: + summary: List all Groups + description: Returns a list of all Groups + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Group + description: Creates a Group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Group request + content: + 'application/json': + schema: + $ref: '#/components/schemas/GroupRequest' + responses: + '200': + description: A Group Object + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/groups/{groupId}: + get: + summary: Retrieve a Group + description: Get information about a Group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: groupId + required: true + schema: + type: string + description: The unique identifier of a group + responses: + '200': + description: A Group object + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Group + description: Update/Replace a Group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: groupId + required: true + schema: + type: string + description: The unique identifier of a group + requestBody: + description: Update Group request + content: + 'application/json': + schema: + $ref: '#/components/schemas/GroupRequest' + responses: + '200': + description: A Group object + content: + application/json: + schema: + $ref: '#/components/schemas/Group' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Group + description: Delete a Group + tags: [ Groups ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: groupId + required: true + schema: + type: string + description: The unique identifier of a group + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/rules: + get: + summary: List all Rules + description: Returns a list of all Rules + tags: [ Rules ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Rules + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Rule' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Rule + description: Creates a Rule + tags: [ Rules ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Rule request + content: + 'application/json': + schema: + $ref: '#/components/schemas/RuleRequest' + responses: + '200': + description: A Rule Object + content: + application/json: + schema: + $ref: '#/components/schemas/Rule' + /api/rules/{ruleId}: + get: + summary: Retrieve a Rule + description: Get information about a Rules + tags: [ Rules ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: ruleId + required: true + schema: + type: string + description: The unique identifier of a rule + responses: + '200': + description: A Rule object + content: + application/json: + schema: + $ref: '#/components/schemas/Rule' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Rule + description: Update/Replace a Rule + tags: [ Rules ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: ruleId + required: true + schema: + type: string + description: The unique identifier of a rule + requestBody: + description: Update Rule request + content: + 'application/json': + schema: + $ref: '#/components/schemas/RuleRequest' + responses: + '200': + description: A Rule object + content: + application/json: + schema: + $ref: '#/components/schemas/Rule' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Rule + description: Delete a Rule + tags: [ Rules ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: ruleId + required: true + schema: + type: string + description: The unique identifier of a rule + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/policies: + get: + summary: List all Policies + description: Returns a list of all Policies + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Policies + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Policy' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Policy + description: Creates a Policy + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Policy request + content: + 'application/json': + schema: + $ref: '#/components/schemas/PolicyMinimum' + responses: + '200': + description: A Policy Object + content: + application/json: + schema: + $ref: '#/components/schemas/Policy' + /api/policies/{policyId}: + get: + summary: Retrieve a Policy + description: Get information about a Policies + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: policyId + required: true + schema: + type: string + description: The unique identifier of a policy + responses: + '200': + description: A Policy object + content: + application/json: + schema: + $ref: '#/components/schemas/Policy' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Policy + description: Update/Replace a Policy + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: policyId + required: true + schema: + type: string + description: The unique identifier of a policy + requestBody: + description: Update Policy request + content: + 'application/json': + schema: + $ref: '#/components/schemas/PolicyMinimum' + responses: + '200': + description: A Policy object + content: + application/json: + schema: + $ref: '#/components/schemas/Policy' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Policy + description: Delete a Policy + tags: [ Policies ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: policyId + required: true + schema: + type: string + description: The unique identifier of a policy + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/routes: + get: + summary: List all Routes + description: Returns a list of all routes + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Routes + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Route + description: Creates a Route + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Routes request + content: + 'application/json': + schema: + $ref: '#/components/schemas/RouteRequest' + responses: + '200': + description: A Route Object + content: + application/json: + schema: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + + /api/routes/{routeId}: + get: + summary: Retrieve a Route + description: Get information about a Routes + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: routeId + required: true + schema: + type: string + description: The unique identifier of a route + responses: + '200': + description: A Route object + content: + application/json: + schema: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Route + description: Update/Replace a Route + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: routeId + required: true + schema: + type: string + description: The unique identifier of a route + requestBody: + description: Update Route request + content: + application/json: + schema: + $ref: '#/components/schemas/RouteRequest' + responses: + '200': + description: A Route object + content: + application/json: + schema: + $ref: '#/components/schemas/Route' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Route + description: Delete a Route + tags: [ Routes ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: routeId + required: true + schema: + type: string + description: The unique identifier of a route + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/nameservers: + get: + summary: List all Nameserver Groups + description: Returns a list of all Nameserver Groups + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Nameserver Groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + post: + summary: Create a Nameserver Group + description: Creates a Nameserver Group + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: New Nameserver Groups request + content: + 'application/json': + schema: + $ref: '#/components/schemas/NameserverGroupRequest' + responses: + '200': + description: A Nameserver Groups Object + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/nameservers/{nsgroupId}: + get: + summary: Retrieve a Nameserver Group + description: Get information about a Nameserver Groups + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: nsgroupId + required: true + schema: + type: string + description: The unique identifier of a Nameserver Group + responses: + '200': + description: A Nameserver Group object + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update a Nameserver Group + description: Update/Replace a Nameserver Group + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: nsgroupId + required: true + schema: + type: string + description: The unique identifier of a Nameserver Group + requestBody: + description: Update Nameserver Group request + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroupRequest' + responses: + '200': + description: A Nameserver Group object + content: + application/json: + schema: + $ref: '#/components/schemas/NameserverGroup' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + delete: + summary: Delete a Nameserver Group + description: Delete a Nameserver Group + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + parameters: + - in: path + name: nsgroupId + required: true + schema: + type: string + description: The unique identifier of a Nameserver Group + responses: + '200': + description: Delete status code + content: { } + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/dns/settings: + get: + summary: Retrieve DNS Settings + description: Returns a DNS settings object + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Object of DNS Setting + content: + application/json: + schema: + items: + $ref: '#/components/schemas/DNSSettings' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + put: + summary: Update DNS Settings + description: Updates a DNS settings object + tags: [ DNS ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + requestBody: + description: A DNS settings object + content: + 'application/json': + schema: + $ref: '#/components/schemas/DNSSettings' + responses: + '200': + description: A JSON Object of DNS Setting + content: + application/json: + schema: + $ref: '#/components/schemas/DNSSettings' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" + /api/events: + get: + summary: List all Events + description: Returns a list of all events + tags: [ Events ] + security: + - BearerAuth: [ ] + - TokenAuth: [ ] + responses: + '200': + description: A JSON Array of Events + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Event' + '400': + "$ref": "#/components/responses/bad_request" + '401': + "$ref": "#/components/responses/requires_authentication" + '403': + "$ref": "#/components/responses/forbidden" + '500': + "$ref": "#/components/responses/internal_error" diff --git a/generator/templates/ApiTemplate.ts b/generator/templates/ApiTemplate.ts new file mode 100644 index 00000000..e35a7c81 --- /dev/null +++ b/generator/templates/ApiTemplate.ts @@ -0,0 +1,272 @@ +const template = ` + +export const title = '<%- tag %>' + +<% operations.forEach(function(operation){ %> + +## <%- operation.summary %> {{ tag: '<%- operation.operation.toUpperCase() %>' , label: '<%- operation.path %>' }} + + + + <%- operation.description %> + <% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'path').length > 0){ %> + #### Path Parameters + + <% operation.parameters.filter((parameter) => parameter.in === 'path').forEach(function(parameter){ %> + + <%- parameter.description %> + + <% }); -%> + + <% }; -%> + <% if(operation.parameters && operation.parameters.filter((parameter) => parameter.in === 'query').length > 0){ %> + #### Query Parameters + + <% operation.parameters.filter((parameter) => parameter.in === 'query').forEach(function(parameter){ %> + + <%- parameter.description %> + + <% }); -%> + + <% }; -%> + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + #### Request-Body Parameters + + <% components.get(operation.requestBody?.content['application/json'].schema.$ref.split('/').pop())?.parameters.forEach(function(parameter){ %> + \} + <% if(parameter.enum){ %> + enumList="<%- parameter.enum %>" + <% }; -%> + <% if(parameter.minimum){ %> + min=\{<%- parameter.minimum %>\} + <% }; -%> + <% if(parameter.maximum){ %> + max=\{<%- parameter.maximum %>\} + <% }; -%> + <% if(parameter.minLength){ %> + minLen=\{<%- parameter.minLength %>\} + <% }; -%> + <% if(parameter.maxLength){ %> + maxLen=\{<%- parameter.maxLength %>\} + <% }; -%> > + <%- parameter.description %> + + <% }); -%> + + <% }; -%> + + + + +\`\`\`bash {{ title: 'cURL' }} +<% if(true){ -%> +curl -X <%- operation.operation.toUpperCase() %> <%- operation.fullPath %> \\<% }; -%> +<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %> +-H 'Accept: application/json' \\<% }; -%> +<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> +-H 'Content-Type: application/json' \\<% }; %> +-H 'Authorization: Token ' <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>\\\n--data-raw '<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) -%>'<% }; %> +\`\`\` + +\`\`\`js +const axios = require('axios'); +<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%> +let data = JSON.stringify(<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>);<% }; -%> + +let config = { + method: '<%- operation.operation.toLowerCase() %>', + maxBodyLength: Infinity, + url: '<%- operation.path %>', + <% if(true){%>headers: { <% }; -%> + <% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %> + 'Accept': 'application/json',<% }; -%> + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + 'Content-Type': 'application/json',<% }; %> + 'Authorization': 'Token ' + <% if(true){ -%>}<% }; -%><% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%>,<% }; -%> + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + data : data<% }; %> +}; + +axios(config) +.then((response) => { + console.log(JSON.stringify(response.data)); +}) +.catch((error) => { + console.log(error); +}); +\`\`\` + +\`\`\`python +import requests +import json + +url = "<%- operation.fullPath %>" +<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%> +payload = json.dumps(<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>)<% }; -%> + +<% if(true){%>headers: { <% }; -%> + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + 'Content-Type': 'application/json',<% }; -%> + <% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %> + 'Accept': 'application/json',<% }; %> + 'Authorization': 'Token ' +} + +response = requests.request("<%- operation.operation.toUpperCase() %>", url, headers=headers<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>, data=payload<% }; %>) + +print(response.text) +\`\`\` + +\`\`\`go +package main + +import ( + "fmt" + "strings" + "net/http" + "io/ioutil" +) + +func main() { + + url := "<%- operation.fullPath %>" + method := "<%- operation.operation.toUpperCase() %>" + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + payload := strings.NewReader(\`<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>\`)<% }; -%> + + client := &http.Client { + } + req, err := http.NewRequest(method, url, <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>payload<% } else { %>nil<% }; %>) + + if err != nil { + fmt.Println(err) + return + <% if(true){%>{<% }; -%> + + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + req.Header.Add("Content-Type", "application/json")<% }; -%> + <% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %> + req.Header.Add("Accept", "application/json")<% }; %> + req.Header.Add("Authorization", "Token ") + + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(body)) +} +\`\`\` + +\`\`\`ruby +require "uri" +require "json" +require "net/http" + +url = URI("<%- operation.fullPath %>") + +https = Net::HTTP.new(url.host, url.port) +https.use_ssl = true + +<% if(true){ %>request = Net::HTTP::<%- operation.operation.slice(0,1).toUpperCase() + operation.operation.slice(1).toLowerCase()%>.new(url)<% }; -%> +<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> +request["Content-Type"] = "application/json"<% }; -%> +<% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %> +request["Accept"] = "application/json"<% }; %> +request["Authorization"] = "Token " +<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> +request.body = JSON.dump(<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>)<% }; -%> + +response = https.request(request) +puts response.read_body +\`\`\` + +\`\`\`java +OkHttpClient client = new OkHttpClient().newBuilder() + .build(); +<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ -%> +MediaType mediaType = MediaType.parse("application/json"); +RequestBody body = RequestBody.create(mediaType, '<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>');<% }; %> +Request request = new Request.Builder() + .url("<%- operation.fullPath %>") + <% if(true){ %>.method("<%- operation.operation.toUpperCase() %>"<% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %>, body<% }; %>)<% }; -%> + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + .addHeader("Content-Type", "application/json")<% }; -%> + <% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %> + .addHeader("Accept", "application/json")<% }; %> + .addHeader("Authorization: Token ") + .build(); +Response response = client.newCall(request).execute(); +\`\`\` + +\`\`\`php + '<%- operation.fullPath %>', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + <% if(true){ %>CURLOPT_CUSTOMREQUEST => '<%- operation.operation.toUpperCase() %>',<% }; -%> + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + CURLOPT_POSTFIELDS => '<%- JSON.stringify(components.get(operation.requestBody?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %>',<% }; %> + <% if(true){ %>CURLOPT_HTTPHEADER => array(<% }; -%> + <% if(operation.requestBody?.content && operation.requestBody?.content['application/json']){ %> + 'Content-Type: application/json',<% }; -%> + <% if(operation.responseList[0].content && operation.responseList[0].content['application/json']){ %> + 'Accept: application/json',<% }; %> + 'Authorization: Token ' + ), +)); + +$response = curl_exec($curl); + +curl_close($curl); +echo $response; +\`\`\` + + + <% operation.responseList.forEach(function(response){ %> + <% if(response?.content && response?.content['application/json']){ %> + <% if(response?.content['application/json'].schema.type === 'array'){ %> + +\`\`\`json {{ title: 'Example' }} +<%- JSON.stringify(new Array(components.get(response?.content['application/json'].schema.items.$ref?.split('/').pop())?.example), null, 2) %> +\`\`\` +\`\`\`json {{ title: 'Schema' }} +<%- JSON.stringify(new Array(components.get(response?.content['application/json'].schema.items.$ref?.split('/').pop())?.schema), null, 2) %> +\`\`\` + + <% } else { %> + +\`\`\`json {{ title: 'Example' }} +<%- JSON.stringify(components.get(response?.content['application/json'].schema.$ref?.split('/').pop())?.example, null, 2) %> +\`\`\` +\`\`\`json {{ title: 'Schema' }} +<%- JSON.stringify(components.get(response?.content['application/json'].schema.$ref?.split('/').pop())?.schema, null, 2) %> +\`\`\` + + <% }; -%> + <% }; -%> + <% }); -%> + + + +--- +<% }); -%> +`.trim() + +export default template diff --git a/generator/tsconfig.json b/generator/tsconfig.json new file mode 100644 index 00000000..8615cb56 --- /dev/null +++ b/generator/tsconfig.json @@ -0,0 +1,13 @@ +// generator files need their own tsconfig.json because they don't like "module": "esnext" setting that nextjs requires in the main tsconfig +{ + "compilerOptions": { + "incremental": true, + "noImplicitAny": false, + "baseUrl": ".", + "paths": { + "~/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 00000000..2c8ee2bb --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + } +} diff --git a/mdx/recma.mjs b/mdx/recma.mjs new file mode 100644 index 00000000..48901160 --- /dev/null +++ b/mdx/recma.mjs @@ -0,0 +1,19 @@ +import { mdxAnnotations } from 'mdx-annotations' +import recmaNextjsStaticProps from 'recma-nextjs-static-props' + +function recmaRemoveNamedExports() { + return (tree) => { + tree.body = tree.body.map((node) => { + if (node.type === 'ExportNamedDeclaration') { + return node.declaration + } + return node + }) + } +} + +export const recmaPlugins = [ + mdxAnnotations.recma, + recmaRemoveNamedExports, + recmaNextjsStaticProps, +] diff --git a/mdx/rehype.mjs b/mdx/rehype.mjs new file mode 100644 index 00000000..9c93cab1 --- /dev/null +++ b/mdx/rehype.mjs @@ -0,0 +1,128 @@ +import { mdxAnnotations } from 'mdx-annotations' +import { visit } from 'unist-util-visit' +import rehypeMdxTitle from 'rehype-mdx-title' +import shiki from 'shiki' +import { toString } from 'mdast-util-to-string' +import * as acorn from 'acorn' +import { slugifyWithCounter } from '@sindresorhus/slugify' + +function rehypeParseCodeBlocks() { + return (tree) => { + visit(tree, 'element', (node, _nodeIndex, parentNode) => { + if (node.tagName === 'code' && node.properties.className) { + parentNode.properties.language = node.properties.className[0]?.replace( + /^language-/, + '' + ) + } + }) + } +} + +let highlighter + +function rehypeShiki() { + return async (tree) => { + highlighter = + highlighter ?? (await shiki.getHighlighter({ theme: 'css-variables' })) + + visit(tree, 'element', (node) => { + if (node.tagName === 'pre' && node.children[0]?.tagName === 'code') { + let codeNode = node.children[0] + let textNode = codeNode.children[0] + + node.properties.code = textNode.value + + if (node.properties.language) { + let tokens = highlighter.codeToThemedTokens( + textNode.value, + node.properties.language + ) + + textNode.value = shiki.renderToHtml(tokens, { + elements: { + pre: ({ children }) => children, + code: ({ children }) => children, + line: ({ children }) => `${children}`, + }, + }) + } + } + }) + } +} + +function rehypeSlugify() { + return (tree) => { + let slugify = slugifyWithCounter() + visit(tree, 'element', (node) => { + if ( ['h2', 'h3', 'h4', 'h5', 'h6'].includes(node.tagName) && !node.properties.id) { + node.properties.id = slugify(toString(node)) + } + }) + } +} + +function rehypeAddMDXExports(getExports) { + return (tree) => { + let exports = Object.entries(getExports(tree)) + + for (let [name, value] of exports) { + for (let node of tree.children) { + if ( + node.type === 'mdxjsEsm' && + new RegExp(`export\\s+const\\s+${name}\\s*=`).test(node.value) + ) { + return + } + } + + let exportStr = `export const ${name} = ${value}` + + tree.children.push({ + type: 'mdxjsEsm', + value: exportStr, + data: { + estree: acorn.parse(exportStr, { + sourceType: 'module', + ecmaVersion: 'latest', + }), + }, + }) + } + } +} + +function getSections(node) { + let sections = [] + + for (let child of node.children ?? []) { + if (child.type === 'element' && ['h2', 'h3', 'h4', 'h5', 'h6'].includes(child.tagName)) { + sections.push(`{ + title: ${JSON.stringify(toString(child))}, + id: ${JSON.stringify(child.properties.id)}, + tagName: ${JSON.stringify(child.tagName)}, + tag: ${JSON.stringify(child.tag)}, + ...${child.properties.annotation} + }`) + } else if (child.children) { + sections.push(...getSections(child)) + } + } + + return sections +} + +export const rehypePlugins = [ + mdxAnnotations.rehype, + rehypeParseCodeBlocks, + rehypeShiki, + rehypeSlugify, + rehypeMdxTitle, + [ + rehypeAddMDXExports, + (tree) => ({ + sections: `[${getSections(tree).join()}]`, + }), + ], +] diff --git a/mdx/remark.mjs b/mdx/remark.mjs new file mode 100644 index 00000000..e523464b --- /dev/null +++ b/mdx/remark.mjs @@ -0,0 +1,4 @@ +import { mdxAnnotations } from 'mdx-annotations' +import remarkGfm from 'remark-gfm' + +export const remarkPlugins = [mdxAnnotations.remark, remarkGfm] diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 00000000..50e393d7 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,45 @@ +import nextMDX from '@next/mdx' +import { remarkPlugins } from './mdx/remark.mjs' +import { rehypePlugins } from './mdx/rehype.mjs' +import { recmaPlugins } from './mdx/recma.mjs' +import rehypeSlug from "rehype-slug"; + +const withMDX = nextMDX({ + options: { + remarkPlugins, + // rehypeSlug, + rehypePlugins, + recmaPlugins, + providerImportSource: '@mdx-js/react', + }, +}) + +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'mdx'], + experimental: { + scrollRestoration: true, + }, + redirects: async () => { + return [ + { + source: '/', + destination: '/docs/introduction', + permanent: true, + }, + { + source: '/docs', + destination: '/docs/introduction', + permanent: true, + }, + { + source: '/ipa', + destination: '/ipa/introduction', + permanent: true, + }, + ] + } +} + +export default withMDX(nextConfig) diff --git a/package.json b/package.json index 51fc0e1a..18c5b3ea 100644 --- a/package.json +++ b/package.json @@ -3,40 +3,62 @@ "version": "1.0.0", "private": true, "scripts": { - "docusaurus": "docusaurus", - "start": "docusaurus serve", - "dev": "docusaurus start --host 0.0.0.0", - "build": "docusaurus build", - "swizzle": "docusaurus swizzle", - "deploy": "docusaurus deploy", - "clear": "docusaurus clear", - "serve": "docusaurus serve", - "write-translations": "docusaurus write-translations", - "write-heading-ids": "docusaurus write-heading-ids" - }, - "dependencies": { - "@docusaurus/core": "2.0.0-beta.20", - "@docusaurus/plugin-google-analytics": "^2.0.0-beta.21", - "@docusaurus/preset-classic": "2.0.0-beta.20", - "@mdx-js/react": "^1.6.21", - "@svgr/webpack": "^5.5.0", - "clsx": "^1.1.1", - "file-loader": "^6.2.0", - "prism-react-renderer": "^1.2.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", - "url-loader": "^4.1.1" + "dev": "next dev", + "build": "next build", + "gen": "swagger-codegen generate -i generator/openapi.yml -l openapi -o generator/api.json && npx ts-node generator/index.ts gen --input generator/api.json/openapi.json --output src/pages/ipa/resources", + "start": "next start", + "lint": "next lint" }, "browserslist": { "production": [ ">0.5%", "not dead", - "not op_mini all" + "not op_mini all", + "defaults, not ie <= 11" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] + }, + "dependencies": { + "@algolia/autocomplete-core": "^1.9.2", + "@algolia/autocomplete-preset-algolia": "^1.9.2", + "@headlessui/react": "^1.7.13", + "@mdx-js/loader": "^2.1.5", + "@mdx-js/react": "^2.1.5", + "@next/mdx": "^13.4.1", + "@sindresorhus/slugify": "^2.1.1", + "@tailwindcss/typography": "^0.5.8", + "acorn": "^8.8.2", + "algoliasearch": "^4.17.0", + "autoprefixer": "^10.4.7", + "clsx": "^1.2.0", + "ejs": "^3.1.9", + "focus-visible": "^5.2.0", + "framer-motion": "10.12.9", + "lodash": "^4.17.21", + "mdast-util-to-string": "^3.2.0", + "mdx-annotations": "^0.1.1", + "next": "13.4.1", + "openapi-types": "^12.1.0", + "postcss-focus-visible": "^8.0.2", + "react": "18.2.0", + "react-dom": "18.2.0", + "recma-nextjs-static-props": "^1.0.0", + "rehype-mdx-title": "^2.0.0", + "rehype-slug": "^5.1.0", + "remark-gfm": "^3.0.1", + "shiki": "^0.14.2", + "tailwindcss": "^3.3.0", + "unist-util-visit": "^4.1.2", + "zustand": "^4.3.8" + }, + "devDependencies": { + "eslint": "8.40.0", + "eslint-config-next": "13.4.1", + "prettier": "^2.8.7", + "prettier-plugin-tailwindcss": "^0.2.6" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000..6573c253 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,9 @@ +module.exports = { + plugins: { + tailwindcss: {}, + 'postcss-focus-visible': { + replaceWith: '[data-focus-visible-added]', + }, + autoprefixer: {}, + }, +} diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 00000000..35bb2b2a --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,5 @@ +module.exports = { + singleQuote: true, + semi: false, + plugins: [require('prettier-plugin-tailwindcss')], +} diff --git a/static/img/architecture/acls.png b/public/img/architecture/acls.png similarity index 100% rename from static/img/architecture/acls.png rename to public/img/architecture/acls.png diff --git a/static/img/architecture/high-level-dia.png b/public/img/architecture/high-level-dia.png similarity index 100% rename from static/img/architecture/high-level-dia.png rename to public/img/architecture/high-level-dia.png diff --git a/static/img/architecture/management.png b/public/img/architecture/management.png similarity index 100% rename from static/img/architecture/management.png rename to public/img/architecture/management.png diff --git a/static/img/architecture/mesh.png b/public/img/architecture/mesh.png similarity index 100% rename from static/img/architecture/mesh.png rename to public/img/architecture/mesh.png diff --git a/static/img/architecture/netbird-peer-auto-tagging-newkey.png b/public/img/architecture/netbird-peer-auto-tagging-newkey.png similarity index 100% rename from static/img/architecture/netbird-peer-auto-tagging-newkey.png rename to public/img/architecture/netbird-peer-auto-tagging-newkey.png diff --git a/static/img/architecture/peer-auto-tagging-setupkey.gif b/public/img/architecture/peer-auto-tagging-setupkey.gif similarity index 100% rename from static/img/architecture/peer-auto-tagging-setupkey.gif rename to public/img/architecture/peer-auto-tagging-setupkey.gif diff --git a/static/img/architecture/relay.png b/public/img/architecture/relay.png similarity index 100% rename from static/img/architecture/relay.png rename to public/img/architecture/relay.png diff --git a/static/img/architecture/setup-keys.png b/public/img/architecture/setup-keys.png similarity index 100% rename from static/img/architecture/setup-keys.png rename to public/img/architecture/setup-keys.png diff --git a/static/img/architecture/signal.png b/public/img/architecture/signal.png similarity index 100% rename from static/img/architecture/signal.png rename to public/img/architecture/signal.png diff --git a/static/img/examples/wiretrustee-on-aws-ecs.png b/public/img/examples/wiretrustee-on-aws-ecs.png similarity index 100% rename from static/img/examples/wiretrustee-on-aws-ecs.png rename to public/img/examples/wiretrustee-on-aws-ecs.png diff --git a/static/img/favicon.ico b/public/img/favicon.ico similarity index 100% rename from static/img/favicon.ico rename to public/img/favicon.ico diff --git a/public/img/getting-started/add-peer.png b/public/img/getting-started/add-peer.png new file mode 100644 index 00000000..f8e6932e Binary files /dev/null and b/public/img/getting-started/add-peer.png differ diff --git a/public/img/getting-started/auth.png b/public/img/getting-started/auth.png new file mode 100644 index 00000000..e3de1f96 Binary files /dev/null and b/public/img/getting-started/auth.png differ diff --git a/public/img/getting-started/device-confirmation.png b/public/img/getting-started/device-confirmation.png new file mode 100644 index 00000000..3d53b2b0 Binary files /dev/null and b/public/img/getting-started/device-confirmation.png differ diff --git a/public/img/getting-started/empty-peers.png b/public/img/getting-started/empty-peers.png new file mode 100644 index 00000000..389de4e8 Binary files /dev/null and b/public/img/getting-started/empty-peers.png differ diff --git a/static/img/getting-started/high-level-dia.png b/public/img/getting-started/high-level-dia.png similarity index 100% rename from static/img/getting-started/high-level-dia.png rename to public/img/getting-started/high-level-dia.png diff --git a/public/img/getting-started/img.png b/public/img/getting-started/img.png new file mode 100644 index 00000000..d76fd6e7 Binary files /dev/null and b/public/img/getting-started/img.png differ diff --git a/static/img/getting-started/logo-full.png b/public/img/getting-started/logo-full.png similarity index 100% rename from static/img/getting-started/logo-full.png rename to public/img/getting-started/logo-full.png diff --git a/static/img/getting-started/logo.png b/public/img/getting-started/logo.png similarity index 100% rename from static/img/getting-started/logo.png rename to public/img/getting-started/logo.png diff --git a/static/img/getting-started/netbird-sso-login-cmd.gif b/public/img/getting-started/netbird-sso-login-cmd.gif similarity index 100% rename from static/img/getting-started/netbird-sso-login-cmd.gif rename to public/img/getting-started/netbird-sso-login-cmd.gif diff --git a/static/img/getting-started/netbird-sso-login-ui.gif b/public/img/getting-started/netbird-sso-login-ui.gif similarity index 100% rename from static/img/getting-started/netbird-sso-login-ui.gif rename to public/img/getting-started/netbird-sso-login-ui.gif diff --git a/public/img/getting-started/netbird-up.png b/public/img/getting-started/netbird-up.png new file mode 100644 index 00000000..bfe674a9 Binary files /dev/null and b/public/img/getting-started/netbird-up.png differ diff --git a/static/img/getting-started/peerA.gif b/public/img/getting-started/peerA.gif similarity index 100% rename from static/img/getting-started/peerA.gif rename to public/img/getting-started/peerA.gif diff --git a/static/img/getting-started/peerB.gif b/public/img/getting-started/peerB.gif similarity index 100% rename from static/img/getting-started/peerB.gif rename to public/img/getting-started/peerB.gif diff --git a/static/img/getting-started/peers.gif b/public/img/getting-started/peers.gif similarity index 100% rename from static/img/getting-started/peers.gif rename to public/img/getting-started/peers.gif diff --git a/public/img/getting-started/peers.png b/public/img/getting-started/peers.png new file mode 100644 index 00000000..aeef5368 Binary files /dev/null and b/public/img/getting-started/peers.png differ diff --git a/static/img/getting-started/systray.png b/public/img/getting-started/systray.png similarity index 100% rename from static/img/getting-started/systray.png rename to public/img/getting-started/systray.png diff --git a/static/img/how-to-guides/activity-monitoring.png b/public/img/how-to-guides/activity-monitoring.png similarity index 100% rename from static/img/how-to-guides/activity-monitoring.png rename to public/img/how-to-guides/activity-monitoring.png diff --git a/static/img/how-to-guides/individual-peer-login-expiration.png b/public/img/how-to-guides/individual-peer-login-expiration.png similarity index 100% rename from static/img/how-to-guides/individual-peer-login-expiration.png rename to public/img/how-to-guides/individual-peer-login-expiration.png diff --git a/static/img/how-to-guides/netbird-nameserver-add-button.png b/public/img/how-to-guides/netbird-nameserver-add-button.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-add-button.png rename to public/img/how-to-guides/netbird-nameserver-add-button.png diff --git a/static/img/how-to-guides/netbird-nameserver-all-group.png b/public/img/how-to-guides/netbird-nameserver-all-group.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-all-group.png rename to public/img/how-to-guides/netbird-nameserver-all-group.png diff --git a/static/img/how-to-guides/netbird-nameserver-custom.png b/public/img/how-to-guides/netbird-nameserver-custom.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-custom.png rename to public/img/how-to-guides/netbird-nameserver-custom.png diff --git a/static/img/how-to-guides/netbird-nameserver-predefined-selected.png b/public/img/how-to-guides/netbird-nameserver-predefined-selected.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-predefined-selected.png rename to public/img/how-to-guides/netbird-nameserver-predefined-selected.png diff --git a/static/img/how-to-guides/netbird-nameserver-remote-resolver.png b/public/img/how-to-guides/netbird-nameserver-remote-resolver.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-remote-resolver.png rename to public/img/how-to-guides/netbird-nameserver-remote-resolver.png diff --git a/static/img/how-to-guides/netbird-nameserver-remote-route.png b/public/img/how-to-guides/netbird-nameserver-remote-route.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-remote-route.png rename to public/img/how-to-guides/netbird-nameserver-remote-route.png diff --git a/static/img/how-to-guides/netbird-nameserver-remote-rule.png b/public/img/how-to-guides/netbird-nameserver-remote-rule.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-remote-rule.png rename to public/img/how-to-guides/netbird-nameserver-remote-rule.png diff --git a/static/img/how-to-guides/netbird-nameserver-selection-view-open.png b/public/img/how-to-guides/netbird-nameserver-selection-view-open.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-selection-view-open.png rename to public/img/how-to-guides/netbird-nameserver-selection-view-open.png diff --git a/static/img/how-to-guides/netbird-nameserver-selection-view.png b/public/img/how-to-guides/netbird-nameserver-selection-view.png similarity index 100% rename from static/img/how-to-guides/netbird-nameserver-selection-view.png rename to public/img/how-to-guides/netbird-nameserver-selection-view.png diff --git a/static/img/how-to-guides/netbird-network-routes-add-button.png b/public/img/how-to-guides/netbird-network-routes-add-button.png similarity index 100% rename from static/img/how-to-guides/netbird-network-routes-add-button.png rename to public/img/how-to-guides/netbird-network-routes-add-button.png diff --git a/static/img/how-to-guides/netbird-network-routes-create-ha.png b/public/img/how-to-guides/netbird-network-routes-create-ha.png similarity index 100% rename from static/img/how-to-guides/netbird-network-routes-create-ha.png rename to public/img/how-to-guides/netbird-network-routes-create-ha.png diff --git a/static/img/how-to-guides/netbird-network-routes-create.png b/public/img/how-to-guides/netbird-network-routes-create.png similarity index 100% rename from static/img/how-to-guides/netbird-network-routes-create.png rename to public/img/how-to-guides/netbird-network-routes-create.png diff --git a/static/img/how-to-guides/netbird-network-routes-masquerading.png b/public/img/how-to-guides/netbird-network-routes-masquerading.png similarity index 100% rename from static/img/how-to-guides/netbird-network-routes-masquerading.png rename to public/img/how-to-guides/netbird-network-routes-masquerading.png diff --git a/static/img/how-to-guides/netbird-network-routes-saved-new-ha.png b/public/img/how-to-guides/netbird-network-routes-saved-new-ha.png similarity index 100% rename from static/img/how-to-guides/netbird-network-routes-saved-new-ha.png rename to public/img/how-to-guides/netbird-network-routes-saved-new-ha.png diff --git a/static/img/how-to-guides/netbird-network-routes-saved-new.png b/public/img/how-to-guides/netbird-network-routes-saved-new.png similarity index 100% rename from static/img/how-to-guides/netbird-network-routes-saved-new.png rename to public/img/how-to-guides/netbird-network-routes-saved-new.png diff --git a/static/img/how-to-guides/netbird-network-routes.png b/public/img/how-to-guides/netbird-network-routes.png similarity index 100% rename from static/img/how-to-guides/netbird-network-routes.png rename to public/img/how-to-guides/netbird-network-routes.png diff --git a/static/img/how-to-guides/peer-login-expiration.png b/public/img/how-to-guides/peer-login-expiration.png similarity index 100% rename from static/img/how-to-guides/peer-login-expiration.png rename to public/img/how-to-guides/peer-login-expiration.png diff --git a/static/img/how-to-guides/peer-needs-login.png b/public/img/how-to-guides/peer-needs-login.png similarity index 100% rename from static/img/how-to-guides/peer-needs-login.png rename to public/img/how-to-guides/peer-needs-login.png diff --git a/static/img/how-to-guides/user-invites.gif b/public/img/how-to-guides/user-invites.gif similarity index 100% rename from static/img/how-to-guides/user-invites.gif rename to public/img/how-to-guides/user-invites.gif diff --git a/static/img/integrations/identity-providers/self-hosted/auth0-create-interactive-login-app.png b/public/img/integrations/identity-providers/self-hosted/auth0-create-interactive-login-app.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/auth0-create-interactive-login-app.png rename to public/img/integrations/identity-providers/self-hosted/auth0-create-interactive-login-app.png diff --git a/static/img/integrations/identity-providers/self-hosted/auth0-grant-types.png b/public/img/integrations/identity-providers/self-hosted/auth0-grant-types.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/auth0-grant-types.png rename to public/img/integrations/identity-providers/self-hosted/auth0-grant-types.png diff --git a/static/img/integrations/identity-providers/self-hosted/auth0-interactive-login-settings.png b/public/img/integrations/identity-providers/self-hosted/auth0-interactive-login-settings.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/auth0-interactive-login-settings.png rename to public/img/integrations/identity-providers/self-hosted/auth0-interactive-login-settings.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-add-application-scope.png b/public/img/integrations/identity-providers/self-hosted/azure-add-application-scope.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-add-application-scope.png rename to public/img/integrations/identity-providers/self-hosted/azure-add-application-scope.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-add-scope.png b/public/img/integrations/identity-providers/self-hosted/azure-add-scope.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-add-scope.png rename to public/img/integrations/identity-providers/self-hosted/azure-add-scope.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-applications-permissions.png b/public/img/integrations/identity-providers/self-hosted/azure-applications-permissions.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-applications-permissions.png rename to public/img/integrations/identity-providers/self-hosted/azure-applications-permissions.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-client-secret.png b/public/img/integrations/identity-providers/self-hosted/azure-client-secret.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-client-secret.png rename to public/img/integrations/identity-providers/self-hosted/azure-client-secret.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-flows-setup.png b/public/img/integrations/identity-providers/self-hosted/azure-flows-setup.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-flows-setup.png rename to public/img/integrations/identity-providers/self-hosted/azure-flows-setup.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-grant-admin-conset.png b/public/img/integrations/identity-providers/self-hosted/azure-grant-admin-conset.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-grant-admin-conset.png rename to public/img/integrations/identity-providers/self-hosted/azure-grant-admin-conset.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-netbird-api-permisssions.png b/public/img/integrations/identity-providers/self-hosted/azure-netbird-api-permisssions.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-netbird-api-permisssions.png rename to public/img/integrations/identity-providers/self-hosted/azure-netbird-api-permisssions.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-new-application.png b/public/img/integrations/identity-providers/self-hosted/azure-new-application.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-new-application.png rename to public/img/integrations/identity-providers/self-hosted/azure-new-application.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-openid-permissions.png b/public/img/integrations/identity-providers/self-hosted/azure-openid-permissions.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-openid-permissions.png rename to public/img/integrations/identity-providers/self-hosted/azure-openid-permissions.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-spa-uri-setup.png b/public/img/integrations/identity-providers/self-hosted/azure-spa-uri-setup.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-spa-uri-setup.png rename to public/img/integrations/identity-providers/self-hosted/azure-spa-uri-setup.png diff --git a/static/img/integrations/identity-providers/self-hosted/azure-user-permissions.png b/public/img/integrations/identity-providers/self-hosted/azure-user-permissions.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/azure-user-permissions.png rename to public/img/integrations/identity-providers/self-hosted/azure-user-permissions.png diff --git a/public/img/integrations/identity-providers/self-hosted/azure_api_scope.png b/public/img/integrations/identity-providers/self-hosted/azure_api_scope.png new file mode 100755 index 00000000..c77feace Binary files /dev/null and b/public/img/integrations/identity-providers/self-hosted/azure_api_scope.png differ diff --git a/static/img/integrations/identity-providers/self-hosted/keycloack-add-client-scope.png b/public/img/integrations/identity-providers/self-hosted/keycloack-add-client-scope.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloack-add-client-scope.png rename to public/img/integrations/identity-providers/self-hosted/keycloack-add-client-scope.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain-category.png b/public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain-category.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain-category.png rename to public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain-category.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain.png b/public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain.png rename to public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper-domain.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper.png b/public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper.png rename to public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claim-mapper.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claims-client.png b/public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claims-client.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claims-client.png rename to public/img/integrations/identity-providers/self-hosted/keycloack-domain-token-claims-client.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-access-settings.png b/public/img/integrations/identity-providers/self-hosted/keycloak-access-settings.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-access-settings.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-access-settings.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-add-role.png b/public/img/integrations/identity-providers/self-hosted/keycloak-add-role.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-add-role.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-add-role.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-auth-grant.gif b/public/img/integrations/identity-providers/self-hosted/keycloak-auth-grant.gif similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-auth-grant.gif rename to public/img/integrations/identity-providers/self-hosted/keycloak-auth-grant.gif diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-backend-client-auth.png b/public/img/integrations/identity-providers/self-hosted/keycloak-backend-client-auth.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-backend-client-auth.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-backend-client-auth.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-backend-client-credentials.png b/public/img/integrations/identity-providers/self-hosted/keycloak-backend-client-credentials.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-backend-client-credentials.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-backend-client-credentials.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper-2.png b/public/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper-2.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper-2.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper-2.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper.png b/public/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-configure-audience-mapper.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-create-backend-client.png b/public/img/integrations/identity-providers/self-hosted/keycloak-create-backend-client.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-create-backend-client.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-create-backend-client.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-create-client-scope.png b/public/img/integrations/identity-providers/self-hosted/keycloak-create-client-scope.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-create-client-scope.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-create-client-scope.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-create-client.png b/public/img/integrations/identity-providers/self-hosted/keycloak-create-client.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-create-client.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-create-client.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-create-realm.png b/public/img/integrations/identity-providers/self-hosted/keycloak-create-realm.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-create-realm.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-create-realm.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-create-user.png b/public/img/integrations/identity-providers/self-hosted/keycloak-create-user.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-create-user.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-create-user.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-domain-token-claim-scope.png b/public/img/integrations/identity-providers/self-hosted/keycloak-domain-token-claim-scope.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-domain-token-claim-scope.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-domain-token-claim-scope.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-enable-auth.png b/public/img/integrations/identity-providers/self-hosted/keycloak-enable-auth.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-enable-auth.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-enable-auth.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-service-account-role.png b/public/img/integrations/identity-providers/self-hosted/keycloak-service-account-role.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-service-account-role.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-service-account-role.png diff --git a/static/img/integrations/identity-providers/self-hosted/keycloak-set-password.png b/public/img/integrations/identity-providers/self-hosted/keycloak-set-password.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/keycloak-set-password.png rename to public/img/integrations/identity-providers/self-hosted/keycloak-set-password.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-create-user.png b/public/img/integrations/identity-providers/self-hosted/zitadel-create-user.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-create-user.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-create-user.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-new-application-auth.png b/public/img/integrations/identity-providers/self-hosted/zitadel-new-application-auth.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-new-application-auth.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-new-application-auth.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-new-application-overview.png b/public/img/integrations/identity-providers/self-hosted/zitadel-new-application-overview.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-new-application-overview.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-new-application-overview.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-new-application-uri.png b/public/img/integrations/identity-providers/self-hosted/zitadel-new-application-uri.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-new-application-uri.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-new-application-uri.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-new-application.png b/public/img/integrations/identity-providers/self-hosted/zitadel-new-application.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-new-application.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-new-application.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-new-project.png b/public/img/integrations/identity-providers/self-hosted/zitadel-new-project.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-new-project.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-new-project.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-redirect-settings.png b/public/img/integrations/identity-providers/self-hosted/zitadel-redirect-settings.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-redirect-settings.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-redirect-settings.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-service-account-role.png b/public/img/integrations/identity-providers/self-hosted/zitadel-service-account-role.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-service-account-role.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-service-account-role.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-service-user-secret.png b/public/img/integrations/identity-providers/self-hosted/zitadel-service-user-secret.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-service-user-secret.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-service-user-secret.png diff --git a/static/img/integrations/identity-providers/self-hosted/zitadel-token-settings.png b/public/img/integrations/identity-providers/self-hosted/zitadel-token-settings.png similarity index 100% rename from static/img/integrations/identity-providers/self-hosted/zitadel-token-settings.png rename to public/img/integrations/identity-providers/self-hosted/zitadel-token-settings.png diff --git a/public/img/logo/logo-api-dark.svg b/public/img/logo/logo-api-dark.svg new file mode 100644 index 00000000..29055bf9 --- /dev/null +++ b/public/img/logo/logo-api-dark.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + API + netbird + + + + + + + diff --git a/public/img/logo/logo-api.svg b/public/img/logo/logo-api.svg new file mode 100644 index 00000000..6bc9a282 --- /dev/null +++ b/public/img/logo/logo-api.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + API + netbird + + + + + + + diff --git a/static/img/logo-dark.svg b/public/img/logo/logo-docs-dark.svg similarity index 95% rename from static/img/logo-dark.svg rename to public/img/logo/logo-docs-dark.svg index 068c330e..1a7d05b9 100644 --- a/static/img/logo-dark.svg +++ b/public/img/logo/logo-docs-dark.svg @@ -8,7 +8,7 @@ version="1.1" id="svg1203" inkscape:version="1.1.1 (1:1.1+202109281944+c3084ef5ed)" - sodipodi:docname="logo-dark.svg" + sodipodi:docname="logo-docs-dark.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -60,7 +60,7 @@ inkscape:groupmode="layer" id="layer1"> + + About NetBird + +
+ {aboutNetbird.map((guide) => ( +
+

+ {guide.name} +

+

+ {guide.description} +

+

+ +

+
+ ))} +
+ + ) +} diff --git a/src/components/Button.jsx b/src/components/Button.jsx new file mode 100644 index 00000000..5603694c --- /dev/null +++ b/src/components/Button.jsx @@ -0,0 +1,62 @@ +import Link from 'next/link' +import clsx from 'clsx' + +function ArrowIcon(props) { + return ( + + ) +} + +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', + 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', + 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', +} + +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 = ( + + ) + + return ( + + {arrow === 'left' && arrowIcon} + {children} + {arrow === 'right' && arrowIcon} + + ) +} diff --git a/src/components/Clients.jsx b/src/components/Clients.jsx new file mode 100644 index 00000000..c864932d --- /dev/null +++ b/src/components/Clients.jsx @@ -0,0 +1,167 @@ +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 clients = [ + { + href: '/docs/client-installation#linux', + name: 'Linux', + description: + 'Learn how to install the NetBird client on Linux devices.', + icon: UserIcon, + pattern: { + y: 16, + squares: [ + [0, 1], + [1, 3], + ], + }, + }, + { + href: '/docs/client-installation#mac-os', + name: 'MacOS', + description: + 'Learn how to install the NetBird client on MacOS devices.', + icon: ChatBubbleIcon, + pattern: { + y: -6, + squares: [ + [-1, 2], + [1, 3], + ], + }, + }, + { + href: '/docs/client-installation#windows', + name: 'Windows', + description: + 'Learn how to install the NetBird client on Windows devices.', + icon: EnvelopeIcon, + pattern: { + y: 32, + squares: [ + [0, 2], + [1, 4], + ], + }, + }, + { + href: '/docs/client-installation#android', + name: 'Android', + description: + 'Learn how to install the NetBird client on Android devices.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, + { + href: '/docs/client-installation#ios', + name: 'iOS', + description: + 'Learn how to install the NetBird client on OS devices.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, +] + +function ClientIcon({ icon: Icon }) { + return ( +
+ +
+ ) +} + +function ClientPattern({ mouseX, mouseY, ...gridProps }) { + let maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)` + let style = { maskImage, WebkitMaskImage: maskImage } + + return ( +
+
+ +
+ + + + +
+ ) +} + +function Client({ client }) { + 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 ( +
+ +
+
+

+ + + {client.name} + +

+

+ {client.description} +

+
+
+ ) +} + +export function Clients() { + return ( +
+ + Clients + +
+ {clients.map((client) => ( + + ))} +
+
+ ) +} diff --git a/src/components/Code.jsx b/src/components/Code.jsx new file mode 100644 index 00000000..602dba23 --- /dev/null +++ b/src/components/Code.jsx @@ -0,0 +1,298 @@ +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', + java: 'Java', +} + +function getPanelTitle({ title, language }) { + return title ?? languageNames[language] ?? 'Code' +} + +function ClipboardIcon(props) { + return ( + + ) +} + +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 ( + + ) +} + +function CodePanelHeader({ tag, label }) { + if (!tag && !label) { + return null + } + + return ( +
+ {tag && ( +
+ {tag} +
+ )} + {tag && label && ( + + )} + {label && ( + {label} + )} +
+ ) +} + +function CodePanel({ tag, label, code, children }) { + let child = Children.only(children) + + return ( +
+ +
+
{children}
+ +
+
+ ) +} + +function CodeGroupHeader({ title, children, selectedIndex }) { + let hasTabs = Children.count(children) > 1 + + if (!title && !hasTabs) { + return null + } + + return ( +
+ {title && ( +

+ {title} +

+ )} + {hasTabs && ( + + {Children.map(children, (child, childIndex) => ( + + {getPanelTitle(child.props)} + + ))} + + )} +
+ ) +} + +function CodeGroupPanels({ children, ...props }) { + let hasTabs = Children.count(children) > 1 + + if (hasTabs) { + return ( + + {Children.map(children, (child) => ( + + {child} + + ))} + + ) + } + + return {children} +} + +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 ( + + + + {children} + + {children} + + + ) +} + +export function Code({ children, ...props }) { + let isGrouped = useContext(CodeGroupContext) + + if (isGrouped) { + return + } + + return {children} +} + +export function Pre({ children, ...props }) { + let isGrouped = useContext(CodeGroupContext) + + if (isGrouped) { + return children + } + + return {children} +} diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx new file mode 100644 index 00000000..7c8ab45a --- /dev/null +++ b/src/components/Footer.jsx @@ -0,0 +1,258 @@ +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} from '@/components/NavigationAPI' +import {docsNavigation} from "@/components/NavigationDocs"; + +function CheckIcon(props) { + return ( + + ) +} + +function FeedbackButton(props) { + return ( + + + {page.title} + + + ) +} + +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 ( +
+ {previousPage && ( +
+ +
+ )} + {nextPage && ( +
+ +
+ )} +
+ ) +} + +function TwitterIcon(props) { + return ( + + ) +} + +function GitHubIcon(props) { + return ( + + ) +} + +function SlackIcon(props) { + return ( + + ) +} + +function DiscordIcon(props) { + return ( + + ) +} + +function SocialLink({ href, icon: Icon, children }) { + return ( + + {children} + + + ) +} + +function SmallPrint() { + return ( +
+

+ © Copyright {new Date().getFullYear()}. All rights reserved. +

+
+ + Follow us on Twitter + + + Follow us on GitHub + + + Join us on Slack + +
+
+ ) +} + +export function Footer() { + let router = useRouter() + + return ( +
+ + + +
+ ) +} diff --git a/src/components/GridPattern.jsx b/src/components/GridPattern.jsx new file mode 100644 index 00000000..d8337656 --- /dev/null +++ b/src/components/GridPattern.jsx @@ -0,0 +1,42 @@ +import { useId } from 'react' + +export function GridPattern({ width, height, x, y, squares, ...props }) { + let patternId = useId() + + return ( + + ) +} diff --git a/src/components/Guides.jsx b/src/components/Guides.jsx new file mode 100644 index 00000000..764ee7fe --- /dev/null +++ b/src/components/Guides.jsx @@ -0,0 +1,43 @@ +import { Button } from '@/components/Button' +import { Heading } from '@/components/Heading' + +const guides = [ + { + href: '/ipa/guides/authentication', + name: 'Authentication', + description: 'Learn how to authenticate your API requests.', + }, + { + href: '/ipa/guides/errors', + name: 'Errors', + description: + 'Read about the different types of errors returned by the API.', + }, +] + +export function Guides() { + return ( +
+ + Guides + +
+ {guides.map((guide) => ( +
+

+ {guide.name} +

+

+ {guide.description} +

+

+ +

+
+ ))} +
+
+ ) +} diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..2a9f9f9f --- /dev/null +++ b/src/components/Header.jsx @@ -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 ( +
  • + + {children} + +
  • + ) +} + +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 ( + +
    + +
    + + + + +
    +
    + +
    +
    + + +
    +
    + +
    +
    + + ) +}) diff --git a/src/components/Heading.jsx b/src/components/Heading.jsx new file mode 100644 index 00000000..997e273b --- /dev/null +++ b/src/components/Heading.jsx @@ -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 ( + + ) +} + +function Eyebrow({ tag, label }) { + if (!tag && !label) { + return null + } + + return ( +
    + {tag && {tag}} + {tag && label && ( + + )} + {label && ( + {label} + )} +
    + ) +} + +function Anchor({ id, inView, children }) { + return ( + + {inView && ( +
    +
    + +
    +
    + )} + {children} + + ) +} + +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 ( + <> + + + {anchor ? ( + + {children} + + ) : ( + children + )} + + + ) +} diff --git a/src/components/HeroPattern.jsx b/src/components/HeroPattern.jsx new file mode 100644 index 00000000..c1b5b086 --- /dev/null +++ b/src/components/HeroPattern.jsx @@ -0,0 +1,32 @@ +import { GridPattern } from '@/components/GridPattern' + +export function HeroPattern() { + return ( +
    +
    +
    + +
    + +
    +
    + ) +} diff --git a/src/components/HomepageFeatures.js b/src/components/HomepageFeatures.js deleted file mode 100644 index 16f820b1..00000000 --- a/src/components/HomepageFeatures.js +++ /dev/null @@ -1,64 +0,0 @@ -import React from 'react'; -import clsx from 'clsx'; -import styles from './HomepageFeatures.module.css'; - -const FeatureList = [ - { - title: 'Easy to Use', - Svg: require('../../static/img/undraw_docusaurus_mountain.svg').default, - description: ( - <> - Docusaurus was designed from the ground up to be easily installed and - used to get your website up and running quickly. - - ), - }, - { - title: 'Focus on What Matters', - Svg: require('../../static/img/undraw_docusaurus_tree.svg').default, - description: ( - <> - Docusaurus lets you focus on your docs, and we'll do the chores. Go - ahead and move your docs into the docs directory. - - ), - }, - { - title: 'Powered by React', - Svg: require('../../static/img/undraw_docusaurus_react.svg').default, - description: ( - <> - Extend or customize your website layout by reusing React. Docusaurus can - be extended while reusing the same header and footer. - - ), - }, -]; - -function Feature({Svg, title, description}) { - return ( -
    -
    - -
    -
    -

    {title}

    -

    {description}

    -
    -
    - ); -} - -export default function HomepageFeatures() { - return ( -
    -
    -
    - {FeatureList.map((props, idx) => ( - - ))} -
    -
    -
    - ); -} diff --git a/src/components/HomepageFeatures.module.css b/src/components/HomepageFeatures.module.css deleted file mode 100644 index b248eb2e..00000000 --- a/src/components/HomepageFeatures.module.css +++ /dev/null @@ -1,11 +0,0 @@ -.features { - display: flex; - align-items: center; - padding: 2rem 0; - width: 100%; -} - -.featureSvg { - height: 200px; - width: 200px; -} diff --git a/src/components/How-To-Guides.jsx b/src/components/How-To-Guides.jsx new file mode 100644 index 00000000..e269debd --- /dev/null +++ b/src/components/How-To-Guides.jsx @@ -0,0 +1,66 @@ +import { Button } from '@/components/Button' +import { Heading } from '@/components/Heading' + +const howToGuides = [ + { + href: '/docs/how-to/getting-started', + name: 'Quickstart guide', + description: 'Start using NetBird in under 5 minutes.', + }, + { + href: '/docs/how-to/manage-network-access', + name: 'Manage network access', + description: + 'Learn how to use access controls to manage access to your machines.', + }, + { + href: '/docs/how-to/add-users-to-your-network', + name: 'Add users to your network', + description: 'learn how to add team members to your NetBird network.', + }, + { + href: '/docs/how-to/routing-traffic-to-private-networks', + name: 'Route traffic to private networks', + description: + 'Learn how to provide access to LANs, VPS, and corporate private networks.', + }, + { + href: '/docs/how-to/monitor-system-and-network-activity', + name: 'Log and monitor network activity', + description: + 'Learn how to keep track of system and network activities in your account.', + }, + { + href: '/docs/how-to/manage-dns-in-your-network', + name: 'Manage DNS in your network', + description: + 'Learn how to configure name servers in your private network.', + }, +] + +export function HowToGuides() { + return ( +
    + + How-To Guides + +
    + {howToGuides.map((guide) => ( +
    +

    + {guide.name} +

    +

    + {guide.description} +

    +

    + +

    +
    + ))} +
    +
    + ) +} diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx new file mode 100644 index 00000000..687778bd --- /dev/null +++ b/src/components/Layout.jsx @@ -0,0 +1,227 @@ +import { useCallback, useEffect, useState } from 'react' +import Link from 'next/link' +import { useRouter } from 'next/router' +import clsx from 'clsx' + +import { Logo, Logomark } from '@/components/Logo' +import { Prose } from '@/components/Prose' +import {HeroPattern} from "@/components/HeroPattern"; +import {NavigationDocs} from "@/components/NavigationDocs"; +import {Header} from "@/components/Header"; +import {NavigationAPI} from "@/components/NavigationAPI"; +import {motion} from "framer-motion"; +import {Footer} from "@/components/Footer"; + +const navigation = [ + { + title: 'Introduction', + links: [ + { title: 'Getting started', href: '/' }, + { title: 'Installation', href: '/docs/installation' }, + ], + }, + { + title: 'Core concepts', + links: [ + { title: 'Understanding caching', href: '/docs/understanding-caching' }, + { + title: 'Predicting user behavior', + href: '/docs/predicting-user-behavior', + }, + { title: 'Basics of time-travel', href: '/docs/basics-of-time-travel' }, + { + title: 'Introduction to string theory', + href: '/docs/introduction-to-string-theory', + }, + { title: 'The butterfly effect', href: '/docs/the-butterfly-effect' }, + ], + }, + { + title: 'Advanced guides', + links: [ + { title: 'Writing plugins', href: '/docs/writing-plugins' }, + { title: 'Neuralink integration', href: '/docs/neuralink-integration' }, + { title: 'Temporal paradoxes', href: '/docs/temporal-paradoxes' }, + { title: 'Testing', href: '/docs/testing' }, + { title: 'Compile-time caching', href: '/docs/compile-time-caching' }, + { + title: 'Predictive data generation', + href: '/docs/predictive-data-generation', + }, + ], + }, + { + title: 'API reference', + links: [ + { title: 'CacheAdvance.predict()', href: '/docs/cacheadvance-predict' }, + { title: 'CacheAdvance.flush()', href: '/docs/cacheadvance-flush' }, + { title: 'CacheAdvance.revert()', href: '/docs/cacheadvance-revert' }, + { title: 'CacheAdvance.regret()', href: '/docs/cacheadvance-regret' }, + ], + }, + { + title: 'Contributing', + links: [ + { title: 'How to contribute', href: '/docs/how-to-contribute' }, + { title: 'Architecture guide', href: '/docs/architecture-guide' }, + { title: 'Design principles', href: '/docs/design-principles' }, + ], + }, +] + +function GitHubIcon(props) { + return ( + + ) +} + +function useTableOfContents(tableOfContents) { + let [currentSection, setCurrentSection] = useState(tableOfContents[0]?.id) + + let getHeadings = useCallback((tableOfContents) => { + return tableOfContents + .flatMap((node) => [node.id, ...node.children.map((child) => child.id)]) + .map((id) => { + let el = document.getElementById(id) + if (!el) return + + let style = window.getComputedStyle(el) + let scrollMt = parseFloat(style.scrollMarginTop) + + let top = window.scrollY + el.getBoundingClientRect().top - scrollMt + return { id, top } + }) + }, []) + + useEffect(() => { + if (tableOfContents.length === 0) return + let headings = getHeadings(tableOfContents) + function onScroll() { + let top = window.scrollY + let current = headings[0]?.id + for (let heading of headings) { + if (top >= heading?.top) { + current = heading?.id + } else { + break + } + } + setCurrentSection(current) + } + window.addEventListener('scroll', onScroll, { passive: true }) + onScroll() + return () => { + window.removeEventListener('scroll', onScroll) + } + }, [getHeadings, tableOfContents]) + + return currentSection +} + +export function Layout({ children, title, tableOfContents }) { + let router = useRouter() + let isHomePage = router.pathname === '/' + let allLinks = navigation.flatMap((section) => section.links) + let linkIndex = allLinks.findIndex((link) => link.href === router.pathname) + let previousPage = allLinks[linkIndex - 1] + let nextPage = allLinks[linkIndex + 1] + let section = navigation.find((section) => + section.links.find((link) => link.href === router.pathname) + ) + + let currentSection = useTableOfContents(tableOfContents) + + function isActive(section) { + if (section.id === currentSection) { + return true + } + if (!section.children) { + return false + } + return section.children.findIndex(isActive) > -1 + } + + return ( + <> + +
    + +
    +
    + + + +
    +
    + {router.route.startsWith("/ipa") && } + {router.route.startsWith("/docs") && } +
    +
    +
    +
    + {children} +
    +
    +
    + {!router.route.startsWith("/ipa/resources") &&
    + +
    } +
    + + ) +} diff --git a/src/components/Libraries.jsx b/src/components/Libraries.jsx new file mode 100644 index 00000000..c3b452c6 --- /dev/null +++ b/src/components/Libraries.jsx @@ -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 ( +
    + + Official libraries + +
    + {libraries.map((library) => ( +
    +
    +

    + {library.name} +

    +

    + {library.description} +

    +

    + +

    +
    + +
    + ))} +
    +
    + ) +} diff --git a/src/components/Logo.jsx b/src/components/Logo.jsx new file mode 100644 index 00000000..9c692ed9 --- /dev/null +++ b/src/components/Logo.jsx @@ -0,0 +1,17 @@ +import {useRouter} from "next/router"; + +export function Logo(props) { + let router = useRouter() + return ( +
    + {router.route.startsWith('/docs') && some file} + {router.route.startsWith('/docs') && some file} + {router.route.startsWith('/ipa') && some file} + {router.route.startsWith('/ipa') && some file} +
    + ) +} diff --git a/src/components/MobileNavigation.jsx b/src/components/MobileNavigation.jsx new file mode 100644 index 00000000..15539b50 --- /dev/null +++ b/src/components/MobileNavigation.jsx @@ -0,0 +1,120 @@ +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 { NavigationAPI } from '@/components/NavigationAPI' +import {NavigationDocs} from "@/components/NavigationDocs"; +import {useRouter} from "next/router"; + +function MenuIcon(props) { + return ( + + ) +} + +function XIcon(props) { + return ( + + ) +} + +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 + + let router = useRouter() + + return ( + + + {!isInsideMobileNavigation && ( + + + +
    + + + + +
    + + + + + {router.route.startsWith("/ipa") ? : + } + + + +
    +
    + )} +
    + ) +} diff --git a/src/components/ModeToggle.jsx b/src/components/ModeToggle.jsx new file mode 100644 index 00000000..f58c6575 --- /dev/null +++ b/src/components/ModeToggle.jsx @@ -0,0 +1,54 @@ +function SunIcon(props) { + return ( + + ) +} + +function MoonIcon(props) { + return ( + + ) +} + +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 ( + + ) +} diff --git a/src/components/NavigationAPI.jsx b/src/components/NavigationAPI.jsx new file mode 100644 index 00000000..f779b927 --- /dev/null +++ b/src/components/NavigationAPI.jsx @@ -0,0 +1,209 @@ +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 { Tag } from '@/components/Tag' +import { remToPx } from '@/lib/remToPx' +import {useIsInsideMobileNavigation} from "@/components/MobileNavigation"; + +export const apiNavigation = [ + { + title: 'Guides', + links: [ + { title: 'Quickstart', href: '/ipa/guides/quickstart' }, + { title: 'Authentication', href: '/ipa/guides/authentication' }, + { title: 'Errors', href: '/ipa/guides/errors' }, + // { title: 'Events', href: '/accounts' }, + ], + }, + { + title: 'Resources', + links: [ + { title: 'Accounts', href: '/ipa/resources/accounts' }, + { title: 'Users', href: '/ipa/resources/users' }, + { title: 'Tokens', href: '/ipa/resources/tokens' }, + { title: 'Peers', href: '/ipa/resources/peers' }, + { title: 'Setup Keys', href: '/ipa/resources/setup-keys' }, + { title: 'Groups', href: '/ipa/resources/groups' }, + { title: 'Rules', href: '/ipa/resources/rules' }, + { title: 'Policies', href: '/ipa/resources/policies' }, + { title: 'Routes', href: '/ipa/resources/routes' }, + { title: 'DNS', href: '/ipa/resources/dns' }, + { title: 'Events', href: '/ipa/resources/events' }, + ], + }, +] + +export function NavigationAPI({tableOfContents, className}) { + return ( + + ) +} +export function TopLevelNavItem({ href, children }) { + return ( +
  • + + {children} + +
  • + ) +} + +export function NavLink({ href, tag, active, isAnchorLink = false, children }) { + return ( + + {children} + {tag && ( + + {tag} + + )} + + ) +} + +export function VisibleSectionHighlight({ group, pathname }) { + let height = remToPx(2) + let offset = remToPx(0) + let activePageIndex = group.links.findIndex((link) => link.href === pathname) + let top = offset + activePageIndex * height + + return ( + + ) +} + +export 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 ( + + ) +} + +function NavigationGroup({ group, className, tableOfContents }) { + let router = useRouter() + + let isActiveGroup = + group.links.findIndex((link) => link.href === router.pathname) !== -1 + + return ( +
  • + + {group.title} + +
    + + {isActiveGroup && ( + + )} + + + + {isActiveGroup && ( + + )} + +
      + {group.links.map((link) => ( + + + {link.title} + + + {link.href === router.pathname && ( + + {router.route.startsWith("/ipa/resources") && tableOfContents?.map((section) => ( +
    • + + {section.title} + +
    • + ))} +
      + )} +
      +
      + ))} +
    +
    +
  • + ) +} + + + diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx new file mode 100644 index 00000000..0e345a8f --- /dev/null +++ b/src/components/NavigationDocs.jsx @@ -0,0 +1,113 @@ +import { useRouter } from 'next/router' +import clsx from 'clsx' +import {ActivePageMarker, NavLink, TopLevelNavItem, VisibleSectionHighlight} from "@/components/NavigationAPI"; +import {AnimatePresence, motion} from "framer-motion"; +import {Button} from "@/components/mdx"; + +export const docsNavigation = [ + { + title: 'About NetBird', + links: [ + { title: 'Why WireGuard with NetBird?', href: '/docs/about-netbird/why-wireguard-with-netbird' }, + { title: 'How NetBird works', href: '/docs/about-netbird/how-netbird-works' }, + { title: 'NetBird vs. traditional VPN', href: '/docs/about-netbird/netbird-vs-traditional-vpn' }, + { title: 'Other', href: '/docs/about-netbird/other' }, + { title: 'FAQ', href: '/docs/about-netbird/faq' }, + ], + }, + { + title: 'How-to guides', + links: [ + { title: 'Getting started', href: '/docs/how-to/getting-started' }, + { title: 'Installation', href: '/docs/how-to/installation' }, + { title: 'Use setup keys for automation', href: '/docs/how-to/register-machines-using-setup-keys' }, + { title: 'Manage network access', href: '/docs/how-to/manage-network-access' }, + { title: 'Add users to your network', href: '/docs/how-to/add-users-to-your-network' }, + { title: 'Access NetBird API', href: '/docs/how-to/access-netbird-public-api' }, + { title: 'Enforce periodic authentication', href: '/docs/how-to/enforce-periodic-user-authentication' }, + { title: 'Monitor system and network activity', href: '/docs/how-to/monitor-system-and-network-activity' }, + { title: 'Route traffic to private networks', href: '/docs/how-to/routing-traffic-to-private-networks' }, + { title: 'Manage DNS in your network', href: '/docs/how-to/manage-dns-in-your-network' }, + { title: 'Examples', href: '/docs/how-to/examples' }, + { title: 'CLI', href: '/docs/how-to/cli' }, + ], + }, + { + title: 'Self-hosting NetBird', + links: [ + { title: 'Installation guide', href: '/docs/selfhosted/selfhosted-guide' }, + { title: 'Supported IdPs', href: '/docs/selfhosted/identity-providers' }, + ], + }, + +] + +export function NavigationDocs({className}) { + return ( + + ) +} + +function NavigationGroup({ group, className }) { + let router = useRouter() + + let isActiveGroup = + group.links.findIndex((link) => link.href === router.pathname) !== -1 + + return ( +
  • + + {group.title} + +
    + + {isActiveGroup && ( + + )} + + + + {isActiveGroup && ( + + )} + +
      + {group.links.map((link) => ( + + + {link.title} + + + ))} +
    +
    +
  • + ) +} diff --git a/src/components/Prose.jsx b/src/components/Prose.jsx new file mode 100644 index 00000000..9fff2b39 --- /dev/null +++ b/src/components/Prose.jsx @@ -0,0 +1,10 @@ +import clsx from 'clsx' + +export function Prose({ as: Component = 'div', className, ...props }) { + return ( + + ) +} diff --git a/src/components/Resources.jsx b/src/components/Resources.jsx new file mode 100644 index 00000000..a2ca8f60 --- /dev/null +++ b/src/components/Resources.jsx @@ -0,0 +1,236 @@ +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: '/ipa/resources/accounts', + name: 'Accounts', + description: + 'Learn how to list and update accounts.', + icon: UserIcon, + pattern: { + y: 16, + squares: [ + [0, 1], + [1, 3], + ], + }, + }, + { + href: '/ipa/resources/users', + name: 'Users', + description: + 'Learn how to create, update, delete, and list users.', + icon: ChatBubbleIcon, + pattern: { + y: -6, + squares: [ + [-1, 2], + [1, 3], + ], + }, + }, + { + href: '/ipa/resources/tokens', + name: 'Tokens', + description: + 'Learn how to create, retrieve, delete, and list tokens.', + icon: ChatBubbleIcon, + pattern: { + y: -6, + squares: [ + [-1, 2], + [1, 3], + ], + }, + }, + { + href: '/ipa/resources/peers', + name: 'Peers', + description: + 'Learn how to retrieve, update, delete, and list peers.', + icon: EnvelopeIcon, + pattern: { + y: 32, + squares: [ + [0, 2], + [1, 4], + ], + }, + }, + { + href: '/ipa/resources/setup-keys', + name: 'Setup Keys', + description: + 'Learn how to create, retrieve, update, and list setup keys.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, + { + href: '/ipa/resources/groups', + name: 'Groups', + description: + 'Learn how to create, retrieve, update, delete, and list groups.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, + { + href: '/ipa/resources/rules', + name: 'Rules', + description: + 'Learn how to create, retrieve, update, delete, and list rules.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, + { + href: '/ipa/resources/policies', + name: 'Policies', + description: + 'Learn how to create, retrieve, update, delete, and list policies.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, + { + href: '/ipa/resources/routes', + name: 'Routes', + description: + 'Learn about how to create, retrieve, update, delete, and list routes.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, + { + href: '/ipa/resources/dns', + name: 'DNS', + description: + 'Learn about how to create, retrieve, update, delete, and list nameserver groups and update and retrieve DNS settings.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, + { + href: '/ipa/resources/events', + name: 'Events', + description: + 'Learn about how to list events.', + icon: UsersIcon, + pattern: { + y: 22, + squares: [[0, 1]], + }, + }, +] + +function ResourceIcon({ icon: Icon }) { + return ( +
    + +
    + ) +} + +function ResourcePattern({ mouseX, mouseY, ...gridProps }) { + let maskImage = useMotionTemplate`radial-gradient(180px at ${mouseX}px ${mouseY}px, white, transparent)` + let style = { maskImage, WebkitMaskImage: maskImage } + + return ( +
    +
    + +
    + + + + +
    + ) +} + +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 ( +
    + +
    +
    +

    + + + {resource.name} + +

    +

    + {resource.description} +

    +
    +
    + ) +} + +export function Resources() { + return ( +
    + + Resources + +
    + {resources.map((resource) => ( + + ))} +
    +
    + ) +} diff --git a/src/components/Search.jsx b/src/components/Search.jsx new file mode 100644 index 00000000..05863f0c --- /dev/null +++ b/src/components/Search.jsx @@ -0,0 +1,515 @@ +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: + '', + highlightPostTag: '', + }, + }, + ], + }) + }, + }, + ] + }, + }) + ) + + 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 ( + + ) +} + +function NoResultsIcon(props) { + return ( + + ) +} + +function LoadingIcon(props) { + let id = useId() + + return ( + + ) +} + +function SearchResult({ result, resultIndex, autocomplete, collection }) { + let id = useId() + let { titleHtml, hierarchyHtml } = resolveResult(result) + + return ( +
  • 0 && 'border-t border-zinc-100 dark:border-zinc-800' + )} + aria-labelledby={`${id}-hierarchy ${id}-title`} + {...autocomplete.getItemProps({ + item: result, + source: collection.source, + })} + > +
  • + ) +} + +function SearchResults({ autocomplete, query, collection }) { + if (collection.items.length === 0) { + return ( +
    + +

    + Nothing found for{' '} + + ‘{query}’ + + . Please try again. +

    +
    + ) + } + + return ( +
      + {collection.items.map((result, resultIndex) => ( + + ))} +
    + ) +} + +const SearchInput = forwardRef(function SearchInput( + { autocomplete, autocompleteState, onClose }, + inputRef +) { + let inputProps = autocomplete.getInputProps({}) + + return ( +
    + + { + 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' && ( +
    + +
    + )} +
    + ) +}) + +function AlgoliaLogo(props) { + return ( + + + + + + + ) +} + +function SearchButton(props) { + let [modifierKey, setModifierKey] = useState() + + useEffect(() => { + setModifierKey( + /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform) ? '⌘' : 'Ctrl ' + ) + }, []) + + return ( + <> + + + + ) +} + +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 ( + autocomplete.setQuery('')} + > + + +
    + + +
    + + +
    +
    + setOpen(false)} + /> +
    + {autocompleteState.isOpen && ( + <> + +

    + Search by{' '} + +

    + + )} +
    + +
    +
    +
    +
    +
    +
    + ) +} + +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 ( +
    + + +
    + ) +} + +export function MobileSearch() { + let { buttonProps, dialogProps } = useSearchProps() + + return ( +
    + + +
    + ) +} diff --git a/src/components/SectionProvider.jsx b/src/components/SectionProvider.jsx new file mode 100644 index 00000000..95f1496c --- /dev/null +++ b/src/components/SectionProvider.jsx @@ -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 ( + + {children} + + ) +} + +export function useSectionStore(selector) { + let store = useContext(SectionStoreContext) + return useStore(store, selector) +} diff --git a/src/components/Tag.jsx b/src/components/Tag.jsx new file mode 100644 index 00000000..902cb8f5 --- /dev/null +++ b/src/components/Tag.jsx @@ -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 ( + + {children} + + ) +} diff --git a/src/components/icons/BellIcon.jsx b/src/components/icons/BellIcon.jsx new file mode 100644 index 00000000..09062dd9 --- /dev/null +++ b/src/components/icons/BellIcon.jsx @@ -0,0 +1,17 @@ +export function BellIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/BoltIcon.jsx b/src/components/icons/BoltIcon.jsx new file mode 100644 index 00000000..1278a7fe --- /dev/null +++ b/src/components/icons/BoltIcon.jsx @@ -0,0 +1,11 @@ +export function BoltIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/BookIcon.jsx b/src/components/icons/BookIcon.jsx new file mode 100644 index 00000000..d560f41c --- /dev/null +++ b/src/components/icons/BookIcon.jsx @@ -0,0 +1,17 @@ +export function BookIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/CalendarIcon.jsx b/src/components/icons/CalendarIcon.jsx new file mode 100644 index 00000000..8d0a57c8 --- /dev/null +++ b/src/components/icons/CalendarIcon.jsx @@ -0,0 +1,23 @@ +export function CalendarIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/CartIcon.jsx b/src/components/icons/CartIcon.jsx new file mode 100644 index 00000000..e4246246 --- /dev/null +++ b/src/components/icons/CartIcon.jsx @@ -0,0 +1,15 @@ +export function CartIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/ChatBubbleIcon.jsx b/src/components/icons/ChatBubbleIcon.jsx new file mode 100644 index 00000000..b929ec64 --- /dev/null +++ b/src/components/icons/ChatBubbleIcon.jsx @@ -0,0 +1,17 @@ +export function ChatBubbleIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/CheckIcon.jsx b/src/components/icons/CheckIcon.jsx new file mode 100644 index 00000000..33d2b249 --- /dev/null +++ b/src/components/icons/CheckIcon.jsx @@ -0,0 +1,17 @@ +export function CheckIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/ChevronRightLeftIcon.jsx b/src/components/icons/ChevronRightLeftIcon.jsx new file mode 100644 index 00000000..2dbaa18c --- /dev/null +++ b/src/components/icons/ChevronRightLeftIcon.jsx @@ -0,0 +1,17 @@ +export function ChevronRightLeftIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/ClipboardIcon.jsx b/src/components/icons/ClipboardIcon.jsx new file mode 100644 index 00000000..9c8c55ce --- /dev/null +++ b/src/components/icons/ClipboardIcon.jsx @@ -0,0 +1,17 @@ +export function ClipboardIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/CogIcon.jsx b/src/components/icons/CogIcon.jsx new file mode 100644 index 00000000..57023fc7 --- /dev/null +++ b/src/components/icons/CogIcon.jsx @@ -0,0 +1,19 @@ +export function CogIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/CopyIcon.jsx b/src/components/icons/CopyIcon.jsx new file mode 100644 index 00000000..aadede17 --- /dev/null +++ b/src/components/icons/CopyIcon.jsx @@ -0,0 +1,17 @@ +export function CopyIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/DocumentIcon.jsx b/src/components/icons/DocumentIcon.jsx new file mode 100644 index 00000000..b576db04 --- /dev/null +++ b/src/components/icons/DocumentIcon.jsx @@ -0,0 +1,17 @@ +export function DocumentIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/EnvelopeIcon.jsx b/src/components/icons/EnvelopeIcon.jsx new file mode 100644 index 00000000..17ab4fa9 --- /dev/null +++ b/src/components/icons/EnvelopeIcon.jsx @@ -0,0 +1,17 @@ +export function EnvelopeIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/FaceSmileIcon.jsx b/src/components/icons/FaceSmileIcon.jsx new file mode 100644 index 00000000..4c01755a --- /dev/null +++ b/src/components/icons/FaceSmileIcon.jsx @@ -0,0 +1,17 @@ +export function FaceSmileIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/FolderIcon.jsx b/src/components/icons/FolderIcon.jsx new file mode 100644 index 00000000..c59b6e0a --- /dev/null +++ b/src/components/icons/FolderIcon.jsx @@ -0,0 +1,22 @@ +export function FolderIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/LinkIcon.jsx b/src/components/icons/LinkIcon.jsx new file mode 100644 index 00000000..2dace231 --- /dev/null +++ b/src/components/icons/LinkIcon.jsx @@ -0,0 +1,12 @@ +export function LinkIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/ListIcon.jsx b/src/components/icons/ListIcon.jsx new file mode 100644 index 00000000..ea45c0a6 --- /dev/null +++ b/src/components/icons/ListIcon.jsx @@ -0,0 +1,17 @@ +export function ListIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/MagnifyingGlassIcon.jsx b/src/components/icons/MagnifyingGlassIcon.jsx new file mode 100644 index 00000000..309359c2 --- /dev/null +++ b/src/components/icons/MagnifyingGlassIcon.jsx @@ -0,0 +1,13 @@ +export function MagnifyingGlassIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/MapPinIcon.jsx b/src/components/icons/MapPinIcon.jsx new file mode 100644 index 00000000..b581a6d6 --- /dev/null +++ b/src/components/icons/MapPinIcon.jsx @@ -0,0 +1,19 @@ +export function MapPinIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/PackageIcon.jsx b/src/components/icons/PackageIcon.jsx new file mode 100644 index 00000000..cb0bc98d --- /dev/null +++ b/src/components/icons/PackageIcon.jsx @@ -0,0 +1,16 @@ +export function PackageIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/PaperAirplaneIcon.jsx b/src/components/icons/PaperAirplaneIcon.jsx new file mode 100644 index 00000000..6f96ea84 --- /dev/null +++ b/src/components/icons/PaperAirplaneIcon.jsx @@ -0,0 +1,17 @@ +export function PaperAirplaneIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/PaperClipIcon.jsx b/src/components/icons/PaperClipIcon.jsx new file mode 100644 index 00000000..cdd72437 --- /dev/null +++ b/src/components/icons/PaperClipIcon.jsx @@ -0,0 +1,12 @@ +export function PaperClipIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/ShapesIcon.jsx b/src/components/icons/ShapesIcon.jsx new file mode 100644 index 00000000..5dd29fd0 --- /dev/null +++ b/src/components/icons/ShapesIcon.jsx @@ -0,0 +1,17 @@ +export function ShapesIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/ShirtIcon.jsx b/src/components/icons/ShirtIcon.jsx new file mode 100644 index 00000000..30c67b92 --- /dev/null +++ b/src/components/icons/ShirtIcon.jsx @@ -0,0 +1,11 @@ +export function ShirtIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/SquaresPlusIcon.jsx b/src/components/icons/SquaresPlusIcon.jsx new file mode 100644 index 00000000..39a507a4 --- /dev/null +++ b/src/components/icons/SquaresPlusIcon.jsx @@ -0,0 +1,17 @@ +export function SquaresPlusIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/TagIcon.jsx b/src/components/icons/TagIcon.jsx new file mode 100644 index 00000000..c7e57b03 --- /dev/null +++ b/src/components/icons/TagIcon.jsx @@ -0,0 +1,19 @@ +export function TagIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/UserIcon.jsx b/src/components/icons/UserIcon.jsx new file mode 100644 index 00000000..350e3c42 --- /dev/null +++ b/src/components/icons/UserIcon.jsx @@ -0,0 +1,24 @@ +export function UserIcon(props) { + return ( + + ) +} diff --git a/src/components/icons/UsersIcon.jsx b/src/components/icons/UsersIcon.jsx new file mode 100644 index 00000000..68ef1312 --- /dev/null +++ b/src/components/icons/UsersIcon.jsx @@ -0,0 +1,28 @@ +export function UsersIcon(props) { + return ( + + ) +} diff --git a/src/components/mdx.jsx b/src/components/mdx.jsx new file mode 100644 index 00000000..3d81159f --- /dev/null +++ b/src/components/mdx.jsx @@ -0,0 +1,124 @@ +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 +} + +export const h3 = function H3(props) { + return +} + +export const h4 = function H4(props) { + return +} + +export const h5 = function H5(props) { + return +} + +function InfoIcon(props) { + return ( + + ) +} + +export function Note({ children }) { + return ( +
    + +
    + {children} +
    +
    + ) +} + +export function Row({ children }) { + return ( +
    + {children} +
    + ) +} + +export function Col({ children, sticky = false }) { + return ( +
    :first-child]:mt-0 [&>:last-child]:mb-0', + sticky && 'xl:sticky xl:top-24' + )} + > + {children} +
    + ) +} + +export function Properties({ children }) { + return ( +
    +
      + {children} +
    +
    + ) +} + +export function Property({ name, type, required, min, max, minLen, maxLen, enumList, children }) { + return ( +
  • +
    +
    Name
    +
    + {name} +
    +
    Type
    +
    + {type} +
    +
    Required
    +
    + {required && 'required'} +
    +
    + {!required && 'optional'} +
    +
    Enum
    +
    + {/*{enumList && "Possible values: [" + enumList.split(',').forEach((type) => ({type})) + "]"}*/} + {/* {enumList &&
    Possible Values: [
    }enumList.split(',').map(type => ''+ type + '')}*/} + {min && !max &&
    Possible Values: >={min}
    } + {max && !min &&
    Possible Values: <={max}
    } + {min && max &&
    Possible Values: >={min} and <={max}
    } + {minLen && !maxLen &&
    Possible Values: >={minLen} {type === "string" ? "characters" : "objects"}
    } + {maxLen && !minLen &&
    Possible Values: <={maxLen} {type === "string" ? "characters" : "objects"}
    } + {minLen && maxLen &&
    Possible Values: >={minLen} {type === "string" ? "characters" : "objects"} and <={maxLen} {type === "string" ? "characters" : "objects"}
    } +
    +
    Description
    +
    + {children} +
    +
    +
  • + ) +} diff --git a/src/css/custom.css b/src/css/custom.css deleted file mode 100644 index f1b663f9..00000000 --- a/src/css/custom.css +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Any CSS included here will be global. The classic template - * bundles Infima by default. Infima is a CSS framework designed to - * work well for content-centric websites. - */ - -:root { - --site-primary-hue-saturation: 100%; - --ifm-color-primary: #2e2e2e; - --ifm-color-primary-dark: #585656; - --ifm-color-primary-darker: #535252; - --ifm-color-primary-darkest: #454343; - --ifm-color-primary-light: #6c6a6a; - --ifm-color-primary-lighter: #716e6e; - --ifm-color-primary-lightest: #7f7d7d; -} - -.markdown { - --ifm-link-color: #0aa6c2; -} - -.table-of-contents { - --ifm-color-primary: #0aa6c2; -} - -.docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.1); - display: block; - margin: 0 calc(-1 * var(--ifm-pre-padding)); - padding: 0 var(--ifm-pre-padding); -} - -html[data-theme='dark'] .docusaurus-highlight-code-line { - background-color: rgba(0, 0, 0, 0.3); -} -[data-theme='dark'] { - --ifm-color-primary: hsl(var(--site-primary-hue-saturation) 90%); -} - -.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%; -} - -/* CSS */ -.button-5 { - align-items: center; - background-clip: padding-box; - background-color: darkorange; - border: 1px solid transparent; - border-radius: .25rem; - box-shadow: rgba(0, 0, 0, 0.02) 0 1px 3px 0; - box-sizing: border-box; - color: #fff; - cursor: pointer; - display: inline-flex; - font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 16px; - font-weight: 400; - justify-content: center; - line-height: 1.25; - margin: 0; - min-height: 2.5rem; - padding: calc(.875rem - 1px) calc(1.5rem - 1px); - position: relative; - text-decoration: none; - transition: all 250ms; - user-select: none; - -webkit-user-select: none; - touch-action: manipulation; - vertical-align: baseline; - width: auto; -} - -.button-5:hover, -.button-5:focus { - background-color: #fb8332; - box-shadow: rgba(0, 0, 0, 0.1) 0 4px 12px; -} - -.button-5:hover { - transform: translateY(-1px); -} - -.button-5:active { - background-color: #c85000; - box-shadow: rgba(0, 0, 0, .06) 0 2px 4px; - transform: translateY(0); -} - -.button-6 { - align-items: center; - background-clip: padding-box; - background-color: #1890ff; - border: 1px solid transparent; - border-radius: .25rem; - box-shadow: rgba(0, 0, 0, 0.02) 0 1px 3px 0; - box-sizing: border-box; - color: #fff; - cursor: pointer; - display: inline-flex; - font-family: system-ui, -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif; - font-size: 14px; - font-weight: 400; - justify-content: center; - line-height: 1.25; - margin: 0; - /*min-height: 2rem;*/ - height: 1rem; - padding: calc(.875rem - 1px) calc(1.5rem - 1px); - position: relative; - text-decoration: none; - transition: all 250ms; - user-select: none; - -webkit-user-select: none; - touch-action: manipulation; - vertical-align: baseline; - width: auto; -} - -.button-6:hover, -.button-6:focus { - background-color: #85a5ff; - box-shadow: rgba(0, 0, 0, 0.1) 0 4px 12px; -} - -.button-6:hover { - transform: translateY(-1px); -} - -.button-6:active { - background-color: #85a5ff; - box-shadow: rgba(0, 0, 0, .06) 0 2px 4px; - transform: translateY(0); -} diff --git a/src/images/logos/go.svg b/src/images/logos/go.svg new file mode 100644 index 00000000..7f7b19de --- /dev/null +++ b/src/images/logos/go.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/images/logos/node.svg b/src/images/logos/node.svg new file mode 100644 index 00000000..1d09de22 --- /dev/null +++ b/src/images/logos/node.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/images/logos/php.svg b/src/images/logos/php.svg new file mode 100644 index 00000000..0a9ac462 --- /dev/null +++ b/src/images/logos/php.svg @@ -0,0 +1,10 @@ + + + + + diff --git a/src/images/logos/python.svg b/src/images/logos/python.svg new file mode 100644 index 00000000..9bceb587 --- /dev/null +++ b/src/images/logos/python.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/src/images/logos/ruby.svg b/src/images/logos/ruby.svg new file mode 100644 index 00000000..b22a5bf1 --- /dev/null +++ b/src/images/logos/ruby.svg @@ -0,0 +1,4 @@ + + + diff --git a/src/images/static/.nojekyll b/src/images/static/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/src/lib/remToPx.js b/src/lib/remToPx.js new file mode 100644 index 00000000..9ae9628d --- /dev/null +++ b/src/lib/remToPx.js @@ -0,0 +1,8 @@ +export function remToPx(remValue) { + let rootFontSize = + typeof window === 'undefined' + ? 16 + : parseFloat(window.getComputedStyle(document.documentElement).fontSize) + + return parseFloat(remValue) * rootFontSize +} diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx new file mode 100644 index 00000000..3284dcdf --- /dev/null +++ b/src/pages/_app.jsx @@ -0,0 +1,74 @@ +import Head from 'next/head' +import { Router, useRouter } from 'next/router' +import { MDXProvider } from '@mdx-js/react' + +import * as mdxComponents from '@/components/mdx' +import { useMobileNavigationStore } from '@/components/MobileNavigation' + +import '@/styles/tailwind.css' +import 'focus-visible' +import {Layout} from "@/components/Layout"; +import {slugifyWithCounter} from "@sindresorhus/slugify"; + +function onRouteChange() { + useMobileNavigationStore.getState().close() +} + +Router.events.on('routeChangeStart', onRouteChange) +Router.events.on('hashChangeStart', onRouteChange) + +export default function App({ Component, pageProps }) { + let router = useRouter() + let tableOfContents = collectHeadings(pageProps.sections) + return ( + <> + + {router.pathname === '/' ? ( + NetBird Docs + ) : ( + router.pathname.startsWith('/docs') ? + {`${pageProps.title} - NetBird Docs`} : {`${pageProps.title} - NetBird API`} + )} + + + + + + + + + ) +} + +function collectHeadings(sections, slugify = slugifyWithCounter()) { + let output = [] + + if (sections === undefined) { + return [] + } + for (let section of sections) { + if (section.tagName === 'h2' || section.tagName === 'h3') { + let title = section.title + let id = section.id + let tag = section.tag + if (section.tagName === 'h3') { + if (!output[output.length - 1]) { + throw new Error( + 'Cannot add `h3` to table of contents without a preceding `h2`' + ) + } + output[output.length - 1].children.push({ + id, + title, + tag, + }) + } else { + output.push({ id, title, tag, children: [] }) + } + } + + output.push(...collectHeadings(output.children ?? [], slugify)) + } + + return output +} diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx new file mode 100644 index 00000000..db198263 --- /dev/null +++ b/src/pages/_document.jsx @@ -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 ( + + +