android/ui: handle NeedsMachineAuth state

Fixes corp#19119

Adds a variation on the ConnectView to render a header and explainer
text for the NeedsMachineAuth state.  A button to take you directly
to the admin page is presented if you are an admin.

Signed-off-by: Jonathan Nobels <jonathan@tailscale.com>
pull/318/head
Jonathan Nobels 7 months ago committed by Percy Wegmann
parent fe3a46e2f0
commit 72b0e365e9
No known key found for this signature in database
GPG Key ID: 29D8CDEB4C13D48B

@ -26,7 +26,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowDropDown import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.Button import androidx.compose.material3.Button
@ -107,6 +106,7 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode
val stateVal = viewModel.stateRes.collectAsState(initial = R.string.placeholder).value val stateVal = viewModel.stateRes.collectAsState(initial = R.string.placeholder).value
val stateStr = stringResource(id = stateVal) val stateStr = stringResource(id = stateVal)
val netmap = viewModel.netmap.collectAsState(initial = null) val netmap = viewModel.netmap.collectAsState(initial = null)
val isAdmin = viewModel.isAdmin.collectAsState(initial = false).value
ListItem( ListItem(
colors = MaterialTheme.colorScheme.surfaceContainerListItem, colors = MaterialTheme.colorScheme.surfaceContainerListItem,
@ -163,7 +163,7 @@ fun MainView(navigation: MainViewNavigation, viewModel: MainViewModel = viewMode
Ipn.State.NoState, Ipn.State.NoState,
Ipn.State.Starting -> StartingView() Ipn.State.Starting -> StartingView()
else -> { else -> {
ConnectView(state, user, { viewModel.toggleVpn() }, { viewModel.login {} }) ConnectView(state, user, { viewModel.toggleVpn() }, { viewModel.login {} }, isAdmin)
} }
} }
} }
@ -257,7 +257,8 @@ fun ConnectView(
state: Ipn.State, state: Ipn.State,
user: IpnLocal.LoginProfile?, user: IpnLocal.LoginProfile?,
connectAction: () -> Unit, connectAction: () -> Unit,
loginAction: () -> Unit loginAction: () -> Unit,
isAdmin: Boolean,
) { ) {
val handler = LocalUriHandler.current val handler = LocalUriHandler.current
@ -269,10 +270,6 @@ fun ConnectView(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
if (state == Ipn.State.NeedsMachineAuth) { if (state == Ipn.State.NeedsMachineAuth) {
Icon(
modifier = Modifier.size(40.dp),
imageVector = Icons.Outlined.Lock,
contentDescription = "Device requires authentication")
Text( Text(
text = stringResource(id = R.string.machine_auth_required), text = stringResource(id = R.string.machine_auth_required),
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
@ -281,12 +278,13 @@ fun ConnectView(
text = stringResource(id = R.string.machine_auth_explainer), text = stringResource(id = R.string.machine_auth_explainer),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center) textAlign = TextAlign.Center)
Spacer(modifier = Modifier.size(1.dp)) if (isAdmin) {
PrimaryActionButton(onClick = { handler.openUri(Links.ADMIN_URL) }) { PrimaryActionButton(onClick = { handler.openUri(Links.ADMIN_URL) }) {
Text( Text(
text = stringResource(id = R.string.open_admin_console), text = stringResource(id = R.string.open_admin_console),
fontSize = MaterialTheme.typography.titleMedium.fontSize) fontSize = MaterialTheme.typography.titleMedium.fontSize)
} }
}
} else if (state != Ipn.State.NeedsLogin && user != null && !user.isEmpty()) { } else if (state != Ipn.State.NeedsLogin && user != null && !user.isEmpty()) {
Icon( Icon(
painter = painterResource(id = R.drawable.power), painter = painterResource(id = R.drawable.power),

@ -34,6 +34,8 @@ class MainViewModel : IpnViewModel() {
val prefs = Notifier.prefs val prefs = Notifier.prefs
val netmap = Notifier.netmap val netmap = Notifier.netmap
val isAdmin: StateFlow<Boolean> = MutableStateFlow(false)
// The active search term for filtering peers // The active search term for filtering peers
val searchTerm: StateFlow<String> = MutableStateFlow("") val searchTerm: StateFlow<String> = MutableStateFlow("")
@ -53,6 +55,7 @@ class MainViewModel : IpnViewModel() {
it?.let { netmap -> it?.let { netmap ->
peerCategorizer.regenerateGroupedPeers(netmap) peerCategorizer.regenerateGroupedPeers(netmap)
peers.set(peerCategorizer.groupedAndFilteredPeers(searchTerm.value)) peers.set(peerCategorizer.groupedAndFilteredPeers(searchTerm.value))
isAdmin.set(netmap.SelfNode.isAdmin)
} }
} }
} }

Loading…
Cancel
Save