Merge branch 'main' into safresume

Signed-off-by: kari-ts <135075563+kari-ts@users.noreply.github.com>
pull/675/head
kari-ts 4 months ago committed by GitHub
commit 7953292aeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -15,7 +15,8 @@ class Netmap {
var Domain: String, var Domain: String,
var UserProfiles: Map<String, Tailcfg.UserProfile>, var UserProfiles: Map<String, Tailcfg.UserProfile>,
var TKAEnabled: Boolean, var TKAEnabled: Boolean,
var DNS: Tailcfg.DNSConfig? = null var DNS: Tailcfg.DNSConfig? = null,
var AllCaps: List<String> = emptyList()
) { ) {
// Keys are tailcfg.UserIDs thet get stringified // Keys are tailcfg.UserIDs thet get stringified
// Helpers // Helpers
@ -51,5 +52,9 @@ class Netmap {
UserProfiles == other.UserProfiles && UserProfiles == other.UserProfiles &&
TKAEnabled == other.TKAEnabled TKAEnabled == other.TKAEnabled
} }
fun hasCap(capability: String): Boolean {
return AllCaps.contains(capability)
}
} }
} }

@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material3.AlertDialog import androidx.compose.material3.AlertDialog
@ -33,6 +34,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
@ -52,10 +56,14 @@ data class UserSwitcherNav(
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = viewModel()) { fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = viewModel()) {
val users by viewModel.loginProfiles.collectAsState() val users by viewModel.loginProfiles.collectAsState()
val currentUser by viewModel.loggedInUser.collectAsState() val currentUser by viewModel.loggedInUser.collectAsState()
val showHeaderMenu by viewModel.showHeaderMenu.collectAsState() val showHeaderMenu by viewModel.showHeaderMenu.collectAsState()
var showDeleteDialog by remember { mutableStateOf(false) }
val context = LocalContext.current
val netmapState by viewModel.netmap.collectAsState()
val capabilityIsOwner = "https://tailscale.com/cap/is-owner"
val isOwner = netmapState?.hasCap(capabilityIsOwner) == true
Scaffold( Scaffold(
topBar = { topBar = {
@ -145,12 +153,48 @@ fun UserSwitcherView(nav: UserSwitcherNav, viewModel: UserSwitcherViewModel = vi
}) })
} }
Lists.SectionDivider()
Setting.Text(R.string.delete_tailnet, destructive = true) {
showDeleteDialog = true
} }
} }
} }
} }
} }
if (showDeleteDialog) {
AlertDialog(
onDismissRequest = { showDeleteDialog = false },
title = { Text(text = stringResource(R.string.delete_tailnet)) },
text = {
if (isOwner) {
OwnerDeleteDialogText {
val uri = Uri.parse("https://login.tailscale.com/admin/settings/general")
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
}
} else {
Text(stringResource(R.string.request_deletion_nonowner))
}
},
confirmButton = {
TextButton(
onClick = {
val intent =
Intent(Intent.ACTION_VIEW, Uri.parse("https://tailscale.com/contact/support"))
context.startActivity(intent)
showDeleteDialog = false
}) {
Text(text = stringResource(R.string.contact_support))
}
},
dismissButton = {
TextButton(onClick = { showDeleteDialog = false }) {
Text(text = stringResource(R.string.cancel))
}
})
}
}
@Composable @Composable
fun FusMenu( fun FusMenu(
onCustomClick: () -> Unit, onCustomClick: () -> Unit,
@ -178,6 +222,41 @@ fun FusMenu(
} }
} }
@Composable
fun OwnerDeleteDialogText(onSettingsClick: () -> Unit) {
val part1 = stringResource(R.string.request_deletion_owner_part1)
val part2a = stringResource(R.string.request_deletion_owner_part2a)
val part2b = stringResource(R.string.request_deletion_owner_part2b)
val annotatedText = buildAnnotatedString {
append(part1 + " ")
pushStringAnnotation(
tag = "settings", annotation = "https://login.tailscale.com/admin/settings/general")
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
append("Settings > General")
}
pop()
append(" $part2a\n\n") // newline after "Delete tailnet."
append(part2b)
}
val context = LocalContext.current
ClickableText(
text = annotatedText,
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurface),
onClick = { offset ->
annotatedText
.getStringAnnotations(tag = "settings", start = offset, end = offset)
.firstOrNull()
?.let { annotation ->
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(annotation.item))
context.startActivity(intent)
}
})
}
@Composable @Composable
fun MenuItem(text: String, onClick: () -> Unit) { fun MenuItem(text: String, onClick: () -> Unit) {
DropdownMenuItem( DropdownMenuItem(

@ -23,6 +23,7 @@ import com.tailscale.ipn.ui.model.Ipn
import com.tailscale.ipn.ui.model.Ipn.State import com.tailscale.ipn.ui.model.Ipn.State
import com.tailscale.ipn.ui.model.Tailcfg import com.tailscale.ipn.ui.model.Tailcfg
import com.tailscale.ipn.ui.notifier.Notifier import com.tailscale.ipn.ui.notifier.Notifier
import com.tailscale.ipn.ui.util.AndroidTVUtil
import com.tailscale.ipn.ui.util.PeerCategorizer import com.tailscale.ipn.ui.util.PeerCategorizer
import com.tailscale.ipn.ui.util.PeerSet import com.tailscale.ipn.ui.util.PeerSet
import com.tailscale.ipn.ui.util.TimeUtil import com.tailscale.ipn.ui.util.TimeUtil
@ -226,7 +227,7 @@ class MainViewModel(private val vpnViewModel: VpnViewModel) : IpnViewModel() {
} }
fun checkIfTaildropDirectorySelected() { fun checkIfTaildropDirectorySelected() {
if (skipPromptsForAuthKeyLogin()) { if (skipPromptsForAuthKeyLogin() || AndroidTVUtil.isAndroidTV()) {
return return
} }

@ -136,6 +136,19 @@
<string name="invalidAuthKeyTitle">Invalid key</string> <string name="invalidAuthKeyTitle">Invalid key</string>
<string name="custom_control_url_title">Custom control server URL</string> <string name="custom_control_url_title">Custom control server URL</string>
<string name="auth_key_input_title">Auth key</string> <string name="auth_key_input_title">Auth key</string>
<string name="delete_tailnet">Delete tailnet</string>
<string name="contact_support">Contact support</string>
<string name="request_deletion_nonowner">All requests related to the removal or deletion of data are handled by our Support team. To open a request, tap the Contact Support button below to be taken to our contact form in the browser. Complete the form, and a Customer Support Engineer will work with you directly to assist.</string>
<string name="request_deletion_owner_part1">
As the owner of this tailnet, to remove yourself from the tailnet you can either reassign ownership and contact our Support team, or delete the whole tailnet through the admin console. To do the latter, go to
</string>
<string name="request_deletion_owner_part2a">
and look for “Delete tailnet”.
</string>
<string name="request_deletion_owner_part2b">
All requests related to the removal or deletion of data are handled by our Support team. To open a request, tap the Contact Support button below to be taken to our contact form in the browser. Complete the form, and a Customer Support Engineer will work with you directly to assist.
</string>
<!-- Strings for ExitNode picker --> <!-- Strings for ExitNode picker -->
<string name="choose_exit_node">Choose exit node</string> <string name="choose_exit_node">Choose exit node</string>

Loading…
Cancel
Save