client/web: add additional web client metrics logging (#10462)

Add additional web client metric logging. Namely, add logging events for
auth / deauth, enable / disable using exit node, enable / disable SSH,
enable / disable advertise routes, and click events on the device details
button.

Updates https://github.com/tailscale/tailscale/issues/10261

Signed-off-by: Mario Minardi <mario@tailscale.com>
This commit is contained in:
Mario Minardi 2023-12-07 09:24:25 -07:00 committed by GitHub
parent 97f84200ac
commit f5f21c213c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 46 additions and 9 deletions

View File

@ -119,3 +119,11 @@ type MetricType = "counter" | "gauge"
export type MetricName =
| "web_client_advertise_exitnode_enable"
| "web_client_advertise_exitnode_disable"
| "web_client_use_exitnode_enable"
| "web_client_use_exitnode_disable"
| "web_client_ssh_enable"
| "web_client_ssh_disable"
| "web_client_node_connect"
| "web_client_node_disconnect"
| "web_client_advertise_routes_change"
| "web_client_device_details_click"

View File

@ -3,7 +3,7 @@
import cx from "classnames"
import React from "react"
import { apiFetch } from "src/api"
import { apiFetch, incrementMetric } from "src/api"
import ACLTag from "src/components/acl-tag"
import * as Control from "src/components/control-components"
import NiceIP from "src/components/nice-ip"
@ -40,11 +40,13 @@ export default function DeviceDetailsView({
{!readonly && (
<Button
sizeVariant="small"
onClick={() =>
onClick={() => {
// increment metrics before logout as we don't gracefully handle disconnect currently
incrementMetric("web_client_node_disconnect")
apiFetch("/local/v0/logout", "POST")
.then(() => setLocation("/"))
.catch((err) => alert("Logout failed: " + err.message))
}
}}
>
Disconnect
</Button>

View File

@ -3,6 +3,7 @@
import cx from "classnames"
import React, { useMemo } from "react"
import { incrementMetric } from "src/api"
import { ReactComponent as ArrowRight } from "src/assets/icons/arrow-right.svg"
import { ReactComponent as Machine } from "src/assets/icons/machine.svg"
import AddressCard from "src/components/address-copy-card"
@ -68,7 +69,11 @@ export default function HomeView({
disabled={readonly}
/>
)}
<Link className="link font-medium" to="/details">
<Link
className="link font-medium"
to="/details"
onClick={() => incrementMetric("web_client_device_details_click")}
>
View device details &rarr;
</Link>
</div>

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: BSD-3-Clause
import React, { useCallback, useState } from "react"
import { apiFetch } from "src/api"
import { apiFetch, incrementMetric } from "src/api"
import { ReactComponent as TailscaleIcon } from "src/assets/icons/tailscale-icon.svg"
import { NodeData } from "src/hooks/node-data"
import Button from "src/ui/button"
@ -25,7 +25,10 @@ export default function LoginView({
const login = useCallback(
(opt: TailscaleUpOptions) => {
tailscaleUp(opt).then(refreshData)
tailscaleUp(opt).then(() => {
incrementMetric("web_client_node_connect")
refreshData()
})
},
[refreshData]
)

View File

@ -148,9 +148,20 @@ export default function useNodeData() {
setIsPosting(false)
mutate() // refresh data after PATCH finishes
}
const updateMetrics = () => {
// only update metrics if values have changed
if (data?.RunningSSHServer !== d.RunSSH) {
incrementMetric(
d.RunSSH ? "web_client_ssh_enable" : "web_client_ssh_disable"
)
}
}
return apiFetch("/local/v0/prefs", "PATCH", d)
.then(onComplete)
.then(() => {
updateMetrics()
onComplete()
})
.catch((err) => {
onComplete()
alert("Failed to update prefs")
@ -176,6 +187,14 @@ export default function useNodeData() {
: "web_client_advertise_exitnode_disable"
)
}
// useExitNode is the ID of the exit node to use
if (data?.UsingExitNode?.ID !== d.UseExitNode) {
incrementMetric(
d.UseExitNode
? "web_client_use_exitnode_enable"
: "web_client_use_exitnode_disable"
)
}
}
return apiFetch("/routes", "POST", d)
@ -189,7 +208,7 @@ export default function useNodeData() {
throw err
})
},
[mutate, data?.AdvertisingExitNode]
[mutate, data?.AdvertisingExitNode, data?.UsingExitNode?.ID]
)
const nodeUpdaters: NodeUpdaters = useMemo(
@ -209,7 +228,7 @@ export default function useNodeData() {
AdvertiseRoutes: routes,
AdvertiseExitNode: data?.AdvertisingExitNode, // unchanged
UseExitNode: data?.UsingExitNode?.ID, // unchanged
}),
}).then(() => incrementMetric("web_client_advertise_routes_change")),
}),
[
data?.AdvertisingExitNode,