@@ -58,6 +58,7 @@ import PageContainer from "@/layouts/PageContainer";
58
58
import useGroupHelper from "@/modules/groups/useGroupHelper" ;
59
59
import AddRouteDropdownButton from "@/modules/peer/AddRouteDropdownButton" ;
60
60
import PeerRoutesTable from "@/modules/peer/PeerRoutesTable" ;
61
+ import { SelectDropdown } from "@components/select/SelectDropdown" ;
61
62
62
63
export default function PeerPage ( ) {
63
64
const queryParameter = useSearchParams ( ) ;
@@ -82,6 +83,9 @@ function PeerOverview() {
82
83
const [ loginExpiration , setLoginExpiration ] = useState (
83
84
peer . login_expiration_enabled ,
84
85
) ;
86
+ const [ ipv6Enabled , setIpv6Enabled ] = useState (
87
+ peer . ipv6_enabled ,
88
+ ) ;
85
89
const [ selectedGroups , setSelectedGroups , { getAllGroupCalls } ] =
86
90
useGroupHelper ( {
87
91
initial : peerGroups ,
@@ -104,10 +108,11 @@ function PeerOverview() {
104
108
ssh ,
105
109
selectedGroups ,
106
110
loginExpiration ,
111
+ ipv6Enabled
107
112
] ) ;
108
113
109
114
const updatePeer = async ( ) => {
110
- const updateRequest = update ( name , ssh , loginExpiration ) ;
115
+ const updateRequest = update ( name , ssh , loginExpiration , ipv6Enabled ) ;
111
116
const groupCalls = getAllGroupCalls ( ) ;
112
117
const batchCall = groupCalls
113
118
? [ ...groupCalls , updateRequest ]
@@ -118,7 +123,7 @@ function PeerOverview() {
118
123
promise : Promise . all ( batchCall ) . then ( ( ) => {
119
124
mutate ( "/peers/" + peer . id ) ;
120
125
mutate ( "/groups" ) ;
121
- updateHasChangedRef ( [ name , ssh , selectedGroups , loginExpiration ] ) ;
126
+ updateHasChangedRef ( [ name , ssh , selectedGroups , loginExpiration , ipv6Enabled ] ) ;
122
127
} ) ,
123
128
loadingMessage : "Saving the peer..." ,
124
129
} ) ;
@@ -130,49 +135,49 @@ function PeerOverview() {
130
135
< div className = { "p-default py-6 mb-4" } >
131
136
< Breadcrumbs >
132
137
< Breadcrumbs . Item
133
- href = { "/peers" }
134
- label = { "Peers" }
135
- icon = { < PeerIcon size = { 13 } /> }
138
+ href = { "/peers" }
139
+ label = { "Peers" }
140
+ icon = { < PeerIcon size = { 13 } /> }
136
141
/>
137
- < Breadcrumbs . Item label = { peer . ip } active />
142
+ < Breadcrumbs . Item label = { peer . ip } active />
138
143
</ Breadcrumbs >
139
144
140
145
< div className = { "flex justify-between max-w-6xl items-start" } >
141
146
< div >
142
147
< div className = { "flex items-center gap-3" } >
143
148
< h1 className = { "flex items-center gap-3" } >
144
149
< CircleIcon
145
- active = { peer . connected }
146
- size = { 12 }
147
- className = { "mb-[3px] shrink-0" }
150
+ active = { peer . connected }
151
+ size = { 12 }
152
+ className = { "mb-[3px] shrink-0" }
148
153
/>
149
- < TextWithTooltip text = { name } maxChars = { 30 } />
154
+ < TextWithTooltip text = { name } maxChars = { 30 } />
150
155
151
156
< Modal
152
- open = { showEditNameModal }
153
- onOpenChange = { setShowEditNameModal }
157
+ open = { showEditNameModal }
158
+ onOpenChange = { setShowEditNameModal }
154
159
>
155
160
< ModalTrigger >
156
161
< div
157
- className = {
158
- "flex items-center gap-2 dark:text-neutral-300 text-neutral-500 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 py-2 px-3 rounded-md cursor-pointer"
159
- }
162
+ className = {
163
+ "flex items-center gap-2 dark:text-neutral-300 text-neutral-500 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 py-2 px-3 rounded-md cursor-pointer"
164
+ }
160
165
>
161
- < PencilIcon size = { 16 } />
166
+ < PencilIcon size = { 16 } />
162
167
</ div >
163
168
</ ModalTrigger >
164
169
< EditNameModal
165
- onSuccess = { ( newName ) => {
166
- setName ( newName ) ;
167
- setShowEditNameModal ( false ) ;
168
- } }
169
- peer = { peer }
170
- initialName = { name }
171
- key = { showEditNameModal ? 1 : 0 }
170
+ onSuccess = { ( newName ) => {
171
+ setName ( newName ) ;
172
+ setShowEditNameModal ( false ) ;
173
+ } }
174
+ peer = { peer }
175
+ initialName = { name }
176
+ key = { showEditNameModal ? 1 : 0 }
172
177
/>
173
178
</ Modal >
174
179
</ h1 >
175
- < LoginExpiredBadge loginExpired = { peer . login_expired } />
180
+ < LoginExpiredBadge loginExpired = { peer . login_expired } />
176
181
</ div >
177
182
< div className = { "flex items-center gap-8" } >
178
183
< Paragraph className = { "flex items-center" } >
@@ -182,138 +187,181 @@ function PeerOverview() {
182
187
</ div >
183
188
< div className = { "flex gap-4" } >
184
189
< Button
185
- variant = { "default" }
186
- className = { "w-full" }
187
- onClick = { ( ) => router . push ( "/peers" ) }
190
+ variant = { "default" }
191
+ className = { "w-full" }
192
+ onClick = { ( ) => router . push ( "/peers" ) }
188
193
>
189
194
Cancel
190
195
</ Button >
191
196
< Button
192
- variant = { "primary" }
193
- className = { "w-full" }
194
- onClick = { ( ) => updatePeer ( ) }
195
- disabled = { ! hasChanges }
197
+ variant = { "primary" }
198
+ className = { "w-full" }
199
+ onClick = { ( ) => updatePeer ( ) }
200
+ disabled = { ! hasChanges }
196
201
>
197
202
Save Changes
198
203
</ Button >
199
204
</ div >
200
205
</ div >
201
206
202
207
< div className = { "flex gap-10 w-full mt-5 max-w-6xl" } >
203
- < PeerInformationCard peer = { peer } />
208
+ < PeerInformationCard peer = { peer } />
204
209
205
210
< div className = { "flex flex-col gap-6 w-1/2" } >
206
211
< FullTooltip
207
- content = {
208
- < div
209
- className = {
210
- "flex gap-2 items-center !text-nb-gray-300 text-xs"
211
- }
212
- >
213
- < IconInfoCircle size = { 14 } />
214
- < span >
212
+ content = {
213
+ < div
214
+ className = {
215
+ "flex gap-2 items-center !text-nb-gray-300 text-xs"
216
+ }
217
+ >
218
+ < IconInfoCircle size = { 14 } />
219
+ < span >
215
220
Login expiration is disabled for all peers added with an
216
221
setup-key.
217
222
</ span >
218
- </ div >
219
- }
220
- className = { "w-full block" }
221
- disabled = { ! ! peer . user_id }
223
+ </ div >
224
+ }
225
+ className = { "w-full block" }
226
+ disabled = { ! ! peer . user_id }
222
227
>
223
228
< FancyToggleSwitch
224
- disabled = { ! peer . user_id }
225
- value = { loginExpiration }
226
- onChange = { setLoginExpiration }
229
+ disabled = { ! peer . user_id }
230
+ value = { loginExpiration }
231
+ onChange = { setLoginExpiration }
232
+ label = {
233
+ < >
234
+ < IconCloudLock size = { 16 } />
235
+ Login Expiration
236
+ </ >
237
+ }
238
+ helpText = {
239
+ "Enable to require SSO login peers to re-authenticate when their login expires."
240
+ }
241
+ />
242
+ </ FullTooltip >
243
+ < FancyToggleSwitch
244
+ value = { ssh }
245
+ onChange = { ( set ) =>
246
+ ! set
247
+ ? setSsh ( false )
248
+ : openSSHDialog ( ) . then ( ( confirm ) => setSsh ( confirm ) )
249
+ }
227
250
label = {
228
251
< >
229
- < IconCloudLock size = { 16 } />
230
- Login Expiration
252
+ < TerminalSquare size = { 16 } />
253
+ SSH Access
231
254
</ >
232
255
}
233
256
helpText = {
234
- "Enable to require SSO login peers to re-authenticate when their login expires ."
257
+ "Enable the SSH server on this peer to access the machine via an secure shell ."
235
258
}
236
- />
237
- </ FullTooltip >
238
- < FancyToggleSwitch
239
- value = { ssh }
240
- onChange = { ( set ) =>
241
- ! set
242
- ? setSsh ( false )
243
- : openSSHDialog ( ) . then ( ( confirm ) => setSsh ( confirm ) )
244
- }
245
- label = {
246
- < >
247
- < TerminalSquare size = { 16 } />
248
- SSH Access
249
- </ >
250
- }
251
- helpText = {
252
- "Enable the SSH server on this peer to access the machine via an secure shell."
253
- }
254
259
/>
255
260
< div >
256
261
< Label > Assigned Groups</ Label >
257
262
< HelpText >
258
263
Use groups to control what this peer can access.
259
264
</ HelpText >
260
265
< PeerGroupSelector
261
- onChange = { setSelectedGroups }
262
- values = { selectedGroups }
263
- peer = { peer }
266
+ onChange = { setSelectedGroups }
267
+ values = { selectedGroups }
268
+ peer = { peer }
264
269
/>
265
270
</ div >
271
+ < div >
272
+ < Label > IPv6 Support</ Label >
273
+ < HelpText >
274
+ Whether to enable IPv6, disable it, or enable IPv6 if at least one group has it enabled.
275
+ </ HelpText >
276
+ < FullTooltip
277
+ content = {
278
+ < div
279
+ className = {
280
+ "flex gap-2 items-center !text-nb-gray-300 text-xs"
281
+ }
282
+ >
283
+ < IconInfoCircle size = { 14 } />
284
+ < span >
285
+ IPv6 Support requires a recent version of the NetBird client as well as a supported OS (Linux with nftables).
286
+ </ span >
287
+ </ div >
288
+ }
289
+ className = { "w-full block" }
290
+ disabled = { peer . ipv6_supported }
291
+ >
292
+ < SelectDropdown
293
+ disabled = { ! peer . ipv6_supported }
294
+ value = { ipv6Enabled }
295
+ onChange = { setIpv6Enabled }
296
+ options = { [
297
+ { label : "Force enabled" , value : "enabled" } ,
298
+ { label : "Inherit from Groups" , value : "inherit" } ,
299
+ { label : "Force disabled" , value : "disabled" } ,
300
+ ] }
301
+ />
302
+ </ FullTooltip >
303
+ </ div >
266
304
</ div >
267
305
</ div >
268
306
</ div >
269
307
270
- < Separator />
308
+ < Separator />
271
309
272
310
{ isLinux ? (
273
- < div className = { "px-8 py-6" } >
274
- < div className = { "max-w-6xl" } >
275
- < div className = { "flex justify-between items-center" } >
276
- < div >
277
- < h2 > Network Routes</ h2 >
278
- < Paragraph >
279
- Access other networks without installing NetBird on every
280
- resource.
281
- </ Paragraph >
282
- </ div >
283
- < div className = { "inline-flex gap-4 justify-end" } >
311
+ < div className = { "px-8 py-6" } >
312
+ < div className = { "max-w-6xl" } >
313
+ < div className = { "flex justify-between items-center" } >
284
314
< div >
285
- < AddRouteDropdownButton />
315
+ < h2 > Network Routes</ h2 >
316
+ < Paragraph >
317
+ Access other networks without installing NetBird on every
318
+ resource.
319
+ </ Paragraph >
320
+ </ div >
321
+ < div className = { "inline-flex gap-4 justify-end" } >
322
+ < div >
323
+ < AddRouteDropdownButton />
324
+ </ div >
286
325
</ div >
287
326
</ div >
327
+ < PeerRoutesTable peer = { peer } />
288
328
</ div >
289
- < PeerRoutesTable peer = { peer } />
290
329
</ div >
291
- </ div >
292
330
) : null }
293
331
</ RoutesProvider >
294
332
</ PageContainer >
295
333
) ;
296
334
}
297
335
298
- function PeerInformationCard ( { peer } : { peer : Peer } ) {
299
- const { isLoading, getRegionByPeer } = useCountries ( ) ;
336
+ function PeerInformationCard ( { peer} : { peer : Peer } ) {
337
+ const { isLoading, getRegionByPeer} = useCountries ( ) ;
300
338
301
339
const countryText = useMemo ( ( ) => {
302
340
return getRegionByPeer ( peer ) ;
303
341
} , [ getRegionByPeer , peer ] ) ;
304
342
305
343
return (
306
- < Card >
307
- < Card . List >
308
- < Card . ListItem
309
- label = {
310
- < >
311
- < MapPin size = { 16 } />
312
- NetBird IP-Address
313
- </ >
314
- }
315
- value = { peer . ip }
316
- />
344
+ < Card >
345
+ < Card . List >
346
+ < Card . ListItem
347
+ label = {
348
+ < >
349
+ < MapPin size = { 16 } />
350
+ NetBird IPv4-Address
351
+ </ >
352
+ }
353
+ value = { peer . ip }
354
+ />
355
+
356
+ < Card . ListItem
357
+ label = {
358
+ < >
359
+ < MapPin size = { 16 } />
360
+ NetBird IPv6-Address
361
+ </ >
362
+ }
363
+ value = { peer . ip6 }
364
+ />
317
365
318
366
< Card . ListItem
319
367
label = {
0 commit comments