client/web: add confirmation dialogs

Add confirmation dialogs for disconnecting and stopping advertisement
of a subnet route.

Updates #10261

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:
Sonia Appasamy
2023-12-08 18:55:57 -05:00
committed by Sonia Appasamy
parent 69b56462fc
commit a4c7b0574a
9 changed files with 474 additions and 23 deletions

View File

@@ -73,6 +73,11 @@ function WebClient({
currentVersion={node.IPNVersion}
/>
</FeatureRoute>
<Route path="/disconnected">
<Card className="mt-8">
<EmptyState description="You have been disconnected" />
</Card>
</Route>
<Route>
<Card className="mt-8">
<EmptyState description="Page not found" />
@@ -129,6 +134,11 @@ function Header({
}) {
const [loc] = useLocation()
if (loc === "/disconnected") {
// No header on view presented after logout.
return null
}
return (
<>
<div className="flex flex-wrap gap-4 justify-between items-center mb-9 md:mb-12">

View File

@@ -11,6 +11,7 @@ import { UpdateAvailableNotification } from "src/components/update-available"
import { NodeData } from "src/types"
import Button from "src/ui/button"
import Card from "src/ui/card"
import Dialog from "src/ui/dialog"
import QuickCopy from "src/ui/quick-copy"
import { useLocation } from "wouter"
@@ -21,9 +22,6 @@ export default function DeviceDetailsView({
readonly: boolean
node: NodeData
}) {
const api = useAPI()
const [, setLocation] = useLocation()
return (
<>
<h1 className="mb-10">Device details</h1>
@@ -39,16 +37,7 @@ export default function DeviceDetailsView({
})}
/>
</div>
{!readonly && (
<Button
sizeVariant="small"
onClick={() =>
api({ action: "logout" }).then(() => setLocation("/"))
}
>
Disconnect
</Button>
)}
{!readonly && <DisconnectDialog />}
</div>
</Card>
{node.Features["auto-update"] &&
@@ -210,3 +199,33 @@ export default function DeviceDetailsView({
</>
)
}
function DisconnectDialog() {
const api = useAPI()
const [, setLocation] = useLocation()
return (
<Dialog
className="max-w-md"
title="Disconnect"
trigger={<Button sizeVariant="small">Disconnect</Button>}
>
<Dialog.Form
cancelButton
submitButton="Disconnect"
destructive
onSubmit={() => {
api({ action: "logout" })
setLocation("/disconnected")
}}
>
You are about to disconnect this device from your tailnet. To reconnect,
you will be required to re-authenticate this device.
<p className="mt-4 text-sm text-text-muted">
Your connection to this web interface will end as soon as you click
disconnect.
</p>
</Dialog.Form>
</Dialog>
)
}

View File

@@ -11,6 +11,7 @@ import * as Control from "src/components/control-components"
import { NodeData } from "src/types"
import Button from "src/ui/button"
import Card from "src/ui/card"
import Dialog from "src/ui/dialog"
import EmptyState from "src/ui/empty-state"
import Input from "src/ui/input"
@@ -139,9 +140,8 @@ export default function SubnetRouterView({
)}
</div>
{!readonly && (
<Button
sizeVariant="small"
onClick={() =>
<StopAdvertisingDialog
onSubmit={() =>
api({
action: "update-routes",
data: advertisedRoutes.filter(
@@ -149,9 +149,7 @@ export default function SubnetRouterView({
),
})
}
>
Stop advertising
</Button>
/>
)}
</div>
</div>
@@ -179,3 +177,22 @@ export default function SubnetRouterView({
</>
)
}
function StopAdvertisingDialog({ onSubmit }: { onSubmit: () => void }) {
return (
<Dialog
className="max-w-md"
title="Stop advertising route"
trigger={<Button sizeVariant="small">Stop advertising</Button>}
>
<Dialog.Form
cancelButton
submitButton="Stop advertising"
destructive
onSubmit={onSubmit}
>
Any active connections between devices over this route will be broken.
</Dialog.Form>
</Dialog>
)
}