@@ -25,8 +25,8 @@ import LoginExpiredBadge from "@components/ui/LoginExpiredBadge";
2525import TextWithTooltip from "@components/ui/TextWithTooltip" ;
2626import { getOperatingSystem } from "@hooks/useOperatingSystem" ;
2727import useRedirect from "@hooks/useRedirect" ;
28- import { IconCloudLock , IconInfoCircle } from "@tabler/icons-react" ;
2928import useFetchApi from "@utils/api" ;
29+ import { cn } from "@utils/helpers" ;
3030import dayjs from "dayjs" ;
3131import { isEmpty , trim } from "lodash" ;
3232import {
@@ -41,6 +41,7 @@ import {
4141 NetworkIcon ,
4242 PencilIcon ,
4343 TerminalSquare ,
44+ TimerResetIcon ,
4445} from "lucide-react" ;
4546import { useRouter , useSearchParams } from "next/navigation" ;
4647import { toASCII } from "punycode" ;
@@ -61,6 +62,7 @@ import type { Peer } from "@/interfaces/Peer";
6162import PageContainer from "@/layouts/PageContainer" ;
6263import useGroupHelper from "@/modules/groups/useGroupHelper" ;
6364import { AccessiblePeersSection } from "@/modules/peer/AccessiblePeersSection" ;
65+ import { PeerExpirationToggle } from "@/modules/peer/PeerExpirationToggle" ;
6466import { PeerNetworkRoutesSection } from "@/modules/peer/PeerNetworkRoutesSection" ;
6567
6668export default function PeerPage ( ) {
@@ -70,9 +72,16 @@ export default function PeerPage() {
7072
7173 useRedirect ( "/peers" , false , ! peerId ) ;
7274
75+ const peerKey = useMemo ( ( ) => {
76+ let id = peer ?. id ?? "" ;
77+ let ssh = peer ?. ssh_enabled ? "1" : "0" ;
78+ let expiration = peer ?. login_expiration_enabled ? "1" : "0" ;
79+ return `${ id } -${ ssh } -${ expiration } ` ;
80+ } , [ peer ] ) ;
81+
7382 return peer && ! isLoading ? (
7483 < PeerProvider peer = { peer } key = { peerId } >
75- < PeerOverview />
84+ < PeerOverview key = { peerKey } />
7685 </ PeerProvider >
7786 ) : (
7887 < FullScreenLoading />
@@ -89,6 +98,9 @@ function PeerOverview() {
8998 const [ loginExpiration , setLoginExpiration ] = useState (
9099 peer . login_expiration_enabled ,
91100 ) ;
101+ const [ inactivityExpiration , setInactivityExpiration ] = useState (
102+ peer . inactivity_expiration_enabled ,
103+ ) ;
92104 const [ selectedGroups , setSelectedGroups , { getAllGroupCalls } ] =
93105 useGroupHelper ( {
94106 initial : peerGroups ,
@@ -111,10 +123,16 @@ function PeerOverview() {
111123 ssh ,
112124 selectedGroups ,
113125 loginExpiration ,
126+ inactivityExpiration ,
114127 ] ) ;
115128
116129 const updatePeer = async ( ) => {
117- const updateRequest = update ( name , ssh , loginExpiration ) ;
130+ const updateRequest = update ( {
131+ name,
132+ ssh,
133+ loginExpiration,
134+ inactivityExpiration,
135+ } ) ;
118136 const groupCalls = getAllGroupCalls ( ) ;
119137 const batchCall = groupCalls
120138 ? [ ...groupCalls , updateRequest ]
@@ -125,13 +143,19 @@ function PeerOverview() {
125143 promise : Promise . all ( batchCall ) . then ( ( ) => {
126144 mutate ( "/peers/" + peer . id ) ;
127145 mutate ( "/groups" ) ;
128- updateHasChangedRef ( [ name , ssh , selectedGroups , loginExpiration ] ) ;
146+ updateHasChangedRef ( [
147+ name ,
148+ ssh ,
149+ selectedGroups ,
150+ loginExpiration ,
151+ inactivityExpiration ,
152+ ] ) ;
129153 } ) ,
130154 loadingMessage : "Saving the peer..." ,
131155 } ) ;
132156 } ;
133157
134- const { isUser } = useLoggedInUser ( ) ;
158+ const { isUser, isOwnerOrAdmin } = useLoggedInUser ( ) ;
135159
136160 return (
137161 < PageContainer >
@@ -213,53 +237,43 @@ function PeerOverview() {
213237 < div className = { "flex gap-10 w-full mt-5 max-w-6xl" } >
214238 < PeerInformationCard peer = { peer } />
215239
216- < div className = { "flex flex-col gap-6 w-1/2" } >
217- < FullTooltip
218- content = {
240+ < div className = { "flex flex-col gap-6 w-1/2 transition-all" } >
241+ < div >
242+ < PeerExpirationToggle
243+ peer = { peer }
244+ value = { loginExpiration }
245+ icon = { < TimerResetIcon size = { 16 } /> }
246+ onChange = { ( state ) => {
247+ setLoginExpiration ( state ) ;
248+ ! state && setInactivityExpiration ( false ) ;
249+ } }
250+ />
251+ { isOwnerOrAdmin && ! ! peer ?. user_id && (
219252 < 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- </ >
253+ className = { cn (
254+ "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]" ,
255+ ! loginExpiration
256+ ? "opacity-50 pointer-events-none"
257+ : "bg-nb-gray-930/80" ,
242258 ) }
259+ >
260+ < PeerExpirationToggle
261+ peer = { peer }
262+ variant = { "blank" }
263+ value = { inactivityExpiration }
264+ onChange = { setInactivityExpiration }
265+ title = { "Require login after disconnect" }
266+ description = {
267+ "Enable to require authentication after users disconnect from management for 10 minutes."
268+ }
269+ className = {
270+ ! loginExpiration ? "opacity-40 pointer-events-none" : ""
271+ }
272+ />
243273 </ 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 >
274+ ) }
275+ </ div >
276+
263277 < FullTooltip
264278 content = {
265279 < div
@@ -335,7 +349,7 @@ function PeerOverview() {
335349 ) ;
336350}
337351
338- function PeerInformationCard ( { peer } : { peer : Peer } ) {
352+ function PeerInformationCard ( { peer } : Readonly < { peer : Peer } > ) {
339353 const { isLoading, getRegionByPeer } = useCountries ( ) ;
340354
341355 const countryText = useMemo ( ( ) => {
@@ -489,7 +503,7 @@ interface ModalProps {
489503 peer : Peer ;
490504 initialName : string ;
491505}
492- function EditNameModal ( { onSuccess, peer, initialName } : ModalProps ) {
506+ function EditNameModal ( { onSuccess, peer, initialName } : Readonly < ModalProps > ) {
493507 const [ name , setName ] = useState ( initialName ) ;
494508
495509 const isDisabled = useMemo ( ( ) => {
0 commit comments