@@ -23,10 +23,9 @@ import Separator from "@components/Separator";
2323import FullScreenLoading from "@components/ui/FullScreenLoading" ;
2424import LoginExpiredBadge from "@components/ui/LoginExpiredBadge" ;
2525import TextWithTooltip from "@components/ui/TextWithTooltip" ;
26- import { getOperatingSystem } from "@hooks/useOperatingSystem" ;
2726import useRedirect from "@hooks/useRedirect" ;
28- import { IconCloudLock , IconInfoCircle } from "@tabler/icons-react" ;
2927import useFetchApi from "@utils/api" ;
28+ import { cn } from "@utils/helpers" ;
3029import dayjs from "dayjs" ;
3130import { isEmpty , trim } from "lodash" ;
3231import {
@@ -41,6 +40,7 @@ import {
4140 NetworkIcon ,
4241 PencilIcon ,
4342 TerminalSquare ,
43+ TimerResetIcon ,
4444} from "lucide-react" ;
4545import { useRouter , useSearchParams } from "next/navigation" ;
4646import { toASCII } from "punycode" ;
@@ -56,11 +56,11 @@ import PeerProvider, { usePeer } from "@/contexts/PeerProvider";
5656import RoutesProvider from "@/contexts/RoutesProvider" ;
5757import { useLoggedInUser } from "@/contexts/UsersProvider" ;
5858import { useHasChanges } from "@/hooks/useHasChanges" ;
59- import { OperatingSystem } from "@/interfaces/OperatingSystem" ;
6059import type { Peer } from "@/interfaces/Peer" ;
6160import PageContainer from "@/layouts/PageContainer" ;
6261import useGroupHelper from "@/modules/groups/useGroupHelper" ;
6362import { AccessiblePeersSection } from "@/modules/peer/AccessiblePeersSection" ;
63+ import { PeerExpirationToggle } from "@/modules/peer/PeerExpirationToggle" ;
6464import { PeerNetworkRoutesSection } from "@/modules/peer/PeerNetworkRoutesSection" ;
6565
6666export default function PeerPage ( ) {
@@ -70,9 +70,16 @@ export default function PeerPage() {
7070
7171 useRedirect ( "/peers" , false , ! peerId ) ;
7272
73+ const peerKey = useMemo ( ( ) => {
74+ let id = peer ?. id ?? "" ;
75+ let ssh = peer ?. ssh_enabled ? "1" : "0" ;
76+ let expiration = peer ?. login_expiration_enabled ? "1" : "0" ;
77+ return `${ id } -${ ssh } -${ expiration } ` ;
78+ } , [ peer ] ) ;
79+
7380 return peer && ! isLoading ? (
7481 < PeerProvider peer = { peer } key = { peerId } >
75- < PeerOverview />
82+ < PeerOverview key = { peerKey } />
7683 </ PeerProvider >
7784 ) : (
7885 < FullScreenLoading />
@@ -89,20 +96,15 @@ function PeerOverview() {
8996 const [ loginExpiration , setLoginExpiration ] = useState (
9097 peer . login_expiration_enabled ,
9198 ) ;
99+ const [ inactivityExpiration , setInactivityExpiration ] = useState (
100+ peer . inactivity_expiration_enabled ,
101+ ) ;
92102 const [ selectedGroups , setSelectedGroups , { getAllGroupCalls } ] =
93103 useGroupHelper ( {
94104 initial : peerGroups ,
95105 peer,
96106 } ) ;
97107
98- /**
99- * Check the operating system of the peer, if it is linux, then show the routes table, otherwise hide it.
100- */
101- const isLinux = useMemo ( ( ) => {
102- const operatingSystem = getOperatingSystem ( peer . os ) ;
103- return operatingSystem == OperatingSystem . LINUX ;
104- } , [ peer . os ] ) ;
105-
106108 /**
107109 * Detect if there are changes in the peer information, if there are changes, then enable the save button.
108110 */
@@ -111,10 +113,16 @@ function PeerOverview() {
111113 ssh ,
112114 selectedGroups ,
113115 loginExpiration ,
116+ inactivityExpiration ,
114117 ] ) ;
115118
116119 const updatePeer = async ( ) => {
117- const updateRequest = update ( name , ssh , loginExpiration ) ;
120+ const updateRequest = update ( {
121+ name,
122+ ssh,
123+ loginExpiration,
124+ inactivityExpiration,
125+ } ) ;
118126 const groupCalls = getAllGroupCalls ( ) ;
119127 const batchCall = groupCalls
120128 ? [ ...groupCalls , updateRequest ]
@@ -125,13 +133,19 @@ function PeerOverview() {
125133 promise : Promise . all ( batchCall ) . then ( ( ) => {
126134 mutate ( "/peers/" + peer . id ) ;
127135 mutate ( "/groups" ) ;
128- updateHasChangedRef ( [ name , ssh , selectedGroups , loginExpiration ] ) ;
136+ updateHasChangedRef ( [
137+ name ,
138+ ssh ,
139+ selectedGroups ,
140+ loginExpiration ,
141+ inactivityExpiration ,
142+ ] ) ;
129143 } ) ,
130144 loadingMessage : "Saving the peer..." ,
131145 } ) ;
132146 } ;
133147
134- const { isUser } = useLoggedInUser ( ) ;
148+ const { isUser, isOwnerOrAdmin } = useLoggedInUser ( ) ;
135149
136150 return (
137151 < PageContainer >
@@ -213,53 +227,43 @@ function PeerOverview() {
213227 < div className = { "flex gap-10 w-full mt-5 max-w-6xl" } >
214228 < PeerInformationCard peer = { peer } />
215229
216- < div className = { "flex flex-col gap-6 w-1/2" } >
217- < FullTooltip
218- content = {
230+ < div className = { "flex flex-col gap-6 w-1/2 transition-all" } >
231+ < div >
232+ < PeerExpirationToggle
233+ peer = { peer }
234+ value = { loginExpiration }
235+ icon = { < TimerResetIcon size = { 16 } /> }
236+ onChange = { ( state ) => {
237+ setLoginExpiration ( state ) ;
238+ ! state && setInactivityExpiration ( false ) ;
239+ } }
240+ />
241+ { isOwnerOrAdmin && ! ! peer ?. user_id && (
219242 < div
220- className = {
221- "flex gap-2 items-center !text-nb-gray-300 text-xs"
222- }
223- >
224- { ! peer . user_id ? (
225- < >
226- < >
227- < IconInfoCircle size = { 14 } />
228- < span >
229- Login expiration is disabled for all peers added
230- with an setup-key.
231- </ span >
232- </ >
233- </ >
234- ) : (
235- < >
236- < LockIcon size = { 14 } />
237- < span >
238- { `You don't have the required permissions to update this
239- setting.` }
240- </ span >
241- </ >
243+ className = { cn (
244+ "border border-nb-gray-900 border-t-0 rounded-b-md bg-nb-gray-940 px-[1.28rem] pt-3 pb-5 flex flex-col gap-4 mx-[0.25rem]" ,
245+ ! loginExpiration
246+ ? "opacity-50 pointer-events-none"
247+ : "bg-nb-gray-930/80" ,
242248 ) }
249+ >
250+ < PeerExpirationToggle
251+ peer = { peer }
252+ variant = { "blank" }
253+ value = { inactivityExpiration }
254+ onChange = { setInactivityExpiration }
255+ title = { "Require login after disconnect" }
256+ description = {
257+ "Enable to require authentication after users disconnect from management for 10 minutes."
258+ }
259+ className = {
260+ ! loginExpiration ? "opacity-40 pointer-events-none" : ""
261+ }
262+ />
243263 </ div >
244- }
245- className = { "w-full block" }
246- disabled = { ! ! peer . user_id && ! isUser }
247- >
248- < FancyToggleSwitch
249- disabled = { ! peer . user_id || isUser }
250- value = { loginExpiration }
251- onChange = { setLoginExpiration }
252- label = {
253- < >
254- < IconCloudLock size = { 16 } />
255- Login Expiration
256- </ >
257- }
258- helpText = {
259- "Enable to require SSO login peers to re-authenticate when their login expires."
260- }
261- />
262- </ FullTooltip >
264+ ) }
265+ </ div >
266+
263267 < FullTooltip
264268 content = {
265269 < div
@@ -317,7 +321,7 @@ function PeerOverview() {
317321 </ div >
318322 </ div >
319323
320- { isLinux && ! isUser ? (
324+ { ! isUser ? (
321325 < >
322326 < Separator />
323327 < PeerNetworkRoutesSection peer = { peer } />
@@ -335,7 +339,7 @@ function PeerOverview() {
335339 ) ;
336340}
337341
338- function PeerInformationCard ( { peer } : { peer : Peer } ) {
342+ function PeerInformationCard ( { peer } : Readonly < { peer : Peer } > ) {
339343 const { isLoading, getRegionByPeer } = useCountries ( ) ;
340344
341345 const countryText = useMemo ( ( ) => {
@@ -371,14 +375,20 @@ function PeerInformationCard({ peer }: { peer: Peer }) {
371375
372376 < Card . ListItem
373377 copy
374- copyText = { "Domain name " }
378+ copyText = { "DNS label " }
375379 label = {
376380 < >
377381 < Globe size = { 16 } />
378382 Domain Name
379383 </ >
380384 }
385+ className = {
386+ peer ?. extra_dns_labels && peer . extra_dns_labels . length > 0
387+ ? "items-start"
388+ : ""
389+ }
381390 value = { peer . dns_label }
391+ extraText = { peer ?. extra_dns_labels }
382392 />
383393
384394 < Card . ListItem
@@ -489,7 +499,7 @@ interface ModalProps {
489499 peer : Peer ;
490500 initialName : string ;
491501}
492- function EditNameModal ( { onSuccess, peer, initialName } : ModalProps ) {
502+ function EditNameModal ( { onSuccess, peer, initialName } : Readonly < ModalProps > ) {
493503 const [ name , setName ] = useState ( initialName ) ;
494504
495505 const isDisabled = useMemo ( ( ) => {
0 commit comments