11import Breadcrumbs from "@components/Breadcrumbs" ;
2+ import Button from "@components/Button" ;
23import FancyToggleSwitch from "@components/FancyToggleSwitch" ;
4+ import HelpText from "@components/HelpText" ;
5+ import InlineLink from "@components/InlineLink" ;
6+ import { Input } from "@components/Input" ;
7+ import { Label } from "@components/Label" ;
38import { notify } from "@components/Notification" ;
9+ import { useHasChanges } from "@hooks/useHasChanges" ;
410import * as Tabs from "@radix-ui/react-tabs" ;
511import { useApiCall } from "@utils/api" ;
6- import { GlobeIcon , NetworkIcon } from "lucide-react" ;
7- import React , { useState } from "react" ;
12+ import { validator } from "@utils/helpers" ;
13+ import { isNetBirdHosted } from "@utils/netbird" ;
14+ import { ExternalLinkIcon , GlobeIcon , NetworkIcon } from "lucide-react" ;
15+ import React , { useMemo , useState } from "react" ;
816import { useSWRConfig } from "swr" ;
917import SettingsIcon from "@/assets/icons/SettingsIcon" ;
1018import { Account } from "@/interfaces/Account" ;
@@ -13,18 +21,23 @@ type Props = {
1321 account : Account ;
1422} ;
1523
16- export default function NetworkSettingsTab ( { account } : Props ) {
24+ export default function NetworkSettingsTab ( { account } : Readonly < Props > ) {
1725 const { mutate } = useSWRConfig ( ) ;
18- const saveRequest = useApiCall < Account > ( "/accounts/" + account . id ) ;
26+ const saveRequest = useApiCall < Account > ( "/accounts/" + account . id , true ) ;
1927
2028 const [ routingPeerDNSSetting , setRoutingPeerDNSSetting ] = useState (
2129 account . settings . routing_peer_dns_resolution_enabled ,
2230 ) ;
31+ const [ customDNSDomain , setCustomDNSDomain ] = useState (
32+ account . settings . dns_domain || "" ,
33+ ) ;
2334
24- const toggleSetting = async ( toggle : boolean ) => {
35+ const toggleNetworkDNSSetting = async ( toggle : boolean ) => {
2536 notify ( {
26- title : "Save Network Settings" ,
27- description : "Network settings successfully saved." ,
37+ title : "DNS Wildcard Routing" ,
38+ description : `DNS Wildcard Routing successfully ${
39+ toggle ? "enabled" : "disabled"
40+ } .`,
2841 promise : saveRequest
2942 . put ( {
3043 id : account . id ,
@@ -37,10 +50,43 @@ export default function NetworkSettingsTab({ account }: Props) {
3750 setRoutingPeerDNSSetting ( toggle ) ;
3851 mutate ( "/accounts" ) ;
3952 } ) ,
40- loadingMessage : "Saving the network settings ..." ,
53+ loadingMessage : "Updating DNS wildcard setting ..." ,
4154 } ) ;
4255 } ;
4356
57+ const { hasChanges, updateRef } = useHasChanges ( [ customDNSDomain ] ) ;
58+
59+ const saveChanges = async ( ) => {
60+ notify ( {
61+ title : "Custom DNS Domain" ,
62+ description : `Custom DNS Domain successfully updated.` ,
63+ promise : saveRequest
64+ . put ( {
65+ id : account . id ,
66+ settings : {
67+ ...account . settings ,
68+ dns_domain : customDNSDomain || "" ,
69+ } ,
70+ } )
71+ . then ( ( ) => {
72+ mutate ( "/accounts" ) ;
73+ updateRef ( [ customDNSDomain ] ) ;
74+ } ) ,
75+ loadingMessage : "Updating Custom DNS domain..." ,
76+ } ) ;
77+ } ;
78+
79+ const domainError = useMemo ( ( ) => {
80+ if ( customDNSDomain == "" ) return "" ;
81+ const valid = validator . isValidDomain ( customDNSDomain , {
82+ allowWildcard : false ,
83+ allowOnlyTld : false ,
84+ } ) ;
85+ if ( ! valid ) {
86+ return "Please enter a valid domain, e.g. example.com or intra.example.com" ;
87+ }
88+ } , [ customDNSDomain ] ) ;
89+
4490 return (
4591 < Tabs . Content value = { "networks" } >
4692 < div className = { "p-default py-6 max-w-2xl" } >
@@ -51,36 +97,80 @@ export default function NetworkSettingsTab({ account }: Props) {
5197 icon = { < SettingsIcon size = { 13 } /> }
5298 />
5399 < Breadcrumbs . Item
54- href = { "/settings#network " }
55- label = { "Network " }
100+ href = { "/settings?tab=networks " }
101+ label = { "Networks " }
56102 icon = { < NetworkIcon size = { 14 } /> }
57103 active
58104 />
59105 </ Breadcrumbs >
60106 < div className = { "flex items-start justify-between" } >
61- < h1 > Networks</ h1 >
107+ < div >
108+ < h1 > Networks</ h1 >
109+ </ div >
110+ < Button
111+ variant = { "primary" }
112+ disabled = { ! hasChanges }
113+ onClick = { saveChanges }
114+ >
115+ Save Changes
116+ </ Button >
62117 </ div >
63118
64119 < div className = { "flex flex-col gap-6 w-full mt-8" } >
65120 < div >
66- < FancyToggleSwitch
67- value = { routingPeerDNSSetting }
68- onChange = { toggleSetting }
69- label = {
70- < >
71- < GlobeIcon size = { 15 } />
72- Enable DNS Wildcard Routing
73- </ >
74- }
75- helpText = {
76- < >
77- Allow routing using DNS wildcards. This requires NetBird
78- client v0.35 or higher. Changes will only take effect after
79- restarting the clients.
80- </ >
121+ < div
122+ className = {
123+ "flex flex-col gap-1 sm:flex-row w-full sm:gap-4 items-center"
81124 }
82- />
125+ >
126+ < div className = { "min-w-[330px]" } >
127+ < Label > DNS Domain</ Label >
128+ < HelpText >
129+ Specify a custom DNS domain for your network. This will be
130+ used for all your peers.
131+ </ HelpText >
132+ </ div >
133+ < div className = { "w-full" } >
134+ < Input
135+ placeholder = {
136+ isNetBirdHosted ( ) ? "netbird.cloud" : "netbird.selfhosted"
137+ }
138+ errorTooltip = { true }
139+ errorTooltipPosition = { "top" }
140+ error = { domainError }
141+ value = { customDNSDomain }
142+ onChange = { ( e ) => setCustomDNSDomain ( e . target . value ) }
143+ />
144+ </ div >
145+ </ div >
83146 </ div >
147+
148+ < FancyToggleSwitch
149+ value = { routingPeerDNSSetting }
150+ onChange = { toggleNetworkDNSSetting }
151+ label = {
152+ < >
153+ < GlobeIcon size = { 15 } />
154+ Enable DNS Wildcard Routing
155+ </ >
156+ }
157+ helpText = {
158+ < >
159+ Allow routing using DNS wildcards. This requires NetBird client
160+ v0.35 or higher. Changes will only take effect after restarting
161+ the clients.{ " " }
162+ < InlineLink
163+ href = {
164+ "https://docs.netbird.io/how-to/accessing-entire-domains-within-networks#enabling-dns-wildcard-routing"
165+ }
166+ target = { "_blank" }
167+ >
168+ Learn more
169+ < ExternalLinkIcon size = { 12 } />
170+ </ InlineLink >
171+ </ >
172+ }
173+ />
84174 </ div >
85175 </ div >
86176 </ Tabs . Content >
0 commit comments