@ -1,31 +1,57 @@
import cx from "classnames"
import React from "react"
import React , { useEffect } from "react"
import LegacyClientView from "src/components/views/legacy-client-view"
import LoginClientView from "src/components/views/login-client-view"
import ManagementClientView from "src/components/views/management-client-view"
import ReadonlyClientView from "src/components/views/readonly-client-view"
import useAuth , { AuthResponse , SessionsCallbacks } from "src/hooks/auth"
import useNodeData from "src/hooks/node-data"
import { Route , Switch } from "wouter"
import useNodeData , { NodeData , NodeUpdate } from "src/hooks/node-data"
import { ReactComponent as TailscaleIcon } from "src/icons/tailscale-icon.svg"
import ProfilePic from "src/ui/profile-pic"
import { Link , Route , Switch , useLocation } from "wouter"
import DeviceDetailsView from "./views/device-details-view"
export default function App() {
const { data : auth , loading : loadingAuth , sessions } = useAuth ( )
const { data , refreshData , updateNode } = useNodeData ( )
useEffect ( ( ) = > {
refreshData ( )
} , [ auth , refreshData ] )
return (
< main className = "flex flex-col items-center min-w-sm max-w-lg mx-auto py-14" >
{ loadingAuth ? (
< main className = " min-w-sm max-w-lg mx-auto py-14 px-5 ">
{ loadingAuth || ! data ? (
< div className = "text-center py-14" > Loading . . . < / div > // TODO(sonia): add a loading view
) : (
< >
{ / * T O D O ( s o n i a ) : g e t r i d o f t h e c o n d i t i o n s h e r e o n c e f u l l / r e a d o n l y
* views live on same components * / }
{ data . DebugMode === "full" && auth ? . ok && < Header node = { data } / > }
< Switch >
< Route path = "/" >
< HomeView auth = { auth } sessions = { sessions } / >
< HomeView
auth = { auth }
data = { data }
sessions = { sessions }
refreshData = { refreshData }
updateNode = { updateNode }
/ >
< / Route >
{ data . DebugMode !== "" && (
< >
< Route path = "/details" >
< DeviceDetailsView node = { data } / >
< / Route >
< Route path = "/details" > { /* TODO */ } Device details < / Route >
< Route path = "/subnets" > { /* TODO */ } Subnet router < / Route >
< Route path = "/ssh" > { /* TODO */ } Tailscale SSH server < / Route >
< Route path = "/serve" > { /* TODO */ } Share local content < / Route >
< Route > Page not found < / Route >
< / >
) }
< Route >
< h2 className = "mt-8" > Page not found < / h2 >
< / Route >
< / Switch >
< / >
) }
< / main >
)
@ -33,18 +59,20 @@ export default function App() {
function HomeView ( {
auth ,
data ,
sessions ,
refreshData ,
updateNode ,
} : {
auth? : AuthResponse
data : NodeData
sessions : SessionsCallbacks
refreshData : ( ) = > Promise < void >
updateNode : ( update : NodeUpdate ) = > void
} ) {
const { data , refreshData , updateNode } = useNodeData ( )
return (
< >
{ ! data ? (
< div className = "text-center py-14" > Loading . . . < / div > // TODO(sonia): add a loading view
) : data ? . Status === "NeedsLogin" || data ? . Status === "NoState" ? (
{ data ? . Status === "NeedsLogin" || data ? . Status === "NoState" ? (
// Client not on a tailnet, render login.
< LoginClientView
data = { data }
@ -64,19 +92,47 @@ function HomeView({
updateNode = { updateNode }
/ >
) }
{ data && < Footer licensesURL = { data . LicensesURL } / > }
{ < Footer licensesURL = { data . LicensesURL } / > }
< / >
)
}
export function Footer ( props : { licensesURL : string ; className? : string } ) {
function Header ( { node } : { node : NodeData } ) {
const [ loc ] = useLocation ( )
return (
< footer
className = { cx ( "container max-w-lg mx-auto text-center" , props . className ) }
< >
< div className = "flex justify-between mb-12" >
< TailscaleIcon / >
< div className = "flex" >
< p className = "mr-2" > { node . Profile . LoginName } < / p >
< ProfilePic url = { node . Profile . ProfilePicURL } / >
< / div >
< / div >
{ loc !== "/" && (
< Link
to = "/"
className = "text-indigo-500 font-medium leading-snug block mb-[10px]"
>
& larr ; Back to { node . DeviceName }
< / Link >
) }
< / >
)
}
export function Footer ( {
licensesURL ,
className ,
} : {
licensesURL : string
className? : string
} ) {
return (
< footer className = { cx ( "container max-w-lg mx-auto text-center" , className ) } >
< a
className = "text-xs text-gray-500 hover:text-gray-600"
href = { props . licensesURL }
href = { licensesURL}
>
Open Source Licenses
< / a >