client/web: add endpoint for logging device detail click metric (#10505)

Add an endpoint for logging the device detail click metric to allow for
this metric to be logged without having a valid session which is the
case when in readonly mode.

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

Signed-off-by: Mario Minardi <mario@tailscale.com>
pull/10553/head
Mario Minardi 12 months ago committed by GitHub
parent d8493d4bd5
commit 109929d110
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -369,4 +369,3 @@ export type MetricName =
| "web_client_node_connect" | "web_client_node_connect"
| "web_client_node_disconnect" | "web_client_node_disconnect"
| "web_client_advertise_routes_change" | "web_client_advertise_routes_change"
| "web_client_device_details_click"

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

@ -336,6 +336,9 @@ func (s *Server) authorizeRequest(w http.ResponseWriter, r *http.Request) (ok bo
case r.URL.Path == "/api/data" && r.Method == httpm.GET: case r.URL.Path == "/api/data" && r.Method == httpm.GET:
// Readonly endpoint allowed without valid browser session. // Readonly endpoint allowed without valid browser session.
return true return true
case r.URL.Path == "/api/device-details-click" && r.Method == httpm.POST:
// Special case metric endpoint that is allowed without a browser session.
return true
case strings.HasPrefix(r.URL.Path, "/api/"): case strings.HasPrefix(r.URL.Path, "/api/"):
// All other /api/ endpoints require a valid browser session. // All other /api/ endpoints require a valid browser session.
if err != nil || !session.isAuthorized(s.timeNow()) { if err != nil || !session.isAuthorized(s.timeNow()) {
@ -371,6 +374,8 @@ func (s *Server) serveLoginAPI(w http.ResponseWriter, r *http.Request) {
s.serveGetNodeData(w, r) s.serveGetNodeData(w, r)
case r.URL.Path == "/api/up" && r.Method == httpm.POST: case r.URL.Path == "/api/up" && r.Method == httpm.POST:
s.serveTailscaleUp(w, r) s.serveTailscaleUp(w, r)
case r.URL.Path == "/api/device-details-click" && r.Method == httpm.POST:
s.serveDeviceDetailsClick(w, r)
default: default:
http.Error(w, "invalid endpoint or method", http.StatusNotFound) http.Error(w, "invalid endpoint or method", http.StatusNotFound)
} }
@ -549,6 +554,9 @@ func (s *Server) serveAPI(w http.ResponseWriter, r *http.Request) {
case path == "/routes" && r.Method == httpm.POST: case path == "/routes" && r.Method == httpm.POST:
s.servePostRoutes(w, r) s.servePostRoutes(w, r)
return return
case path == "/device-details-click" && r.Method == httpm.POST:
s.serveDeviceDetailsClick(w, r)
return
case strings.HasPrefix(path, "/local/"): case strings.HasPrefix(path, "/local/"):
s.proxyRequestToLocalAPI(w, r) s.proxyRequestToLocalAPI(w, r)
return return
@ -970,6 +978,21 @@ func (s *Server) serveTailscaleUp(w http.ResponseWriter, r *http.Request) {
} }
} }
// serveDeviceDetailsClick increments the web_client_device_details_click metric
// by one.
//
// Metric logging from the frontend typically is proxied to the localapi. This event
// has been special cased as access to the localapi is gated upon having a valid
// session which is not always the case when we want to be logging this metric (e.g.,
// when in readonly mode).
//
// Other metrics should not be logged in this way without a good reason.
func (s *Server) serveDeviceDetailsClick(w http.ResponseWriter, r *http.Request) {
s.lc.IncrementCounter(r.Context(), "web_client_device_details_click", 1)
io.WriteString(w, "{}")
}
// proxyRequestToLocalAPI proxies the web API request to the localapi. // proxyRequestToLocalAPI proxies the web API request to the localapi.
// //
// The web API request path is expected to exactly match a localapi path, // The web API request path is expected to exactly match a localapi path,

Loading…
Cancel
Save