Compare commits

...

2 Commits

Author SHA1 Message Date
Naman Sood
b190e3140c client/web: fix UI bug in self-update card
Updates #10187. The other cards had been updated to use a slightly different
styling, but this one had not.

Signed-off-by: Naman Sood <mail@nsood.in>
2024-10-24 08:58:50 -05:00
Naman Sood
ad0a21cf0b client/{tailscale,web}: expose self-update progress without session auth
Updates #10187. When tailscaled restarts as part of a self-update, it loses
login sessions, so the frontend can't reach an endpoint behind session auth
to check if the update succeeded or failed. This makes that one endpoint
available without session auth.

Signed-off-by: Naman Sood <mail@nsood.in>
2024-10-24 08:58:42 -05:00
4 changed files with 40 additions and 2 deletions

View File

@@ -1616,6 +1616,22 @@ func (lc *LocalClient) DriveShareList(ctx context.Context) ([]*drive.Share, erro
return shares, err
}
// GetUpdateProgress returns the status of an in-progress Tailscale self-update.
// This is provided as a slice of ipnstate.UpdateProgress structs with various
// log messages in order from oldest to newest. If an update is not in progress,
// the returned slice will be empty.
func (lc *LocalClient) GetUpdateProgress(ctx context.Context) ([]ipnstate.UpdateProgress, error) {
body, err := lc.get200(ctx, "/localapi/v0/update/progress")
if err != nil {
return nil, err
}
ups, err := decodeJSON[[]ipnstate.UpdateProgress](body)
if err != nil {
return nil, err
}
return ups, nil
}
// IPNBusWatcher is an active subscription (watch) of the local tailscaled IPN bus.
// It's returned by LocalClient.WatchIPNBus.
//

View File

@@ -15,7 +15,7 @@ export function UpdateAvailableNotification({
const [, setLocation] = useLocation()
return (
<Card>
<Card noPadding className="-mx-5 p-5 details-card">
<h2 className="mb-2">
Update available{" "}
{details.LatestVersion && `(v${details.LatestVersion})`}

View File

@@ -55,7 +55,7 @@ export function useInstallUpdate(currentVersion: string, cv?: VersionInfo) {
let timer: NodeJS.Timeout | undefined
function poll() {
apiFetch<UpdateProgress[]>("/local/v0/update/progress", "GET")
apiFetch<UpdateProgress[]>("/update/progress", "GET")
.then((res) => {
// res contains a list of UpdateProgresses that is strictly increasing
// in size, so updateMessagesRead keeps track (across calls of poll())

View File

@@ -442,6 +442,8 @@ func (s *Server) serveLoginAPI(w http.ResponseWriter, r *http.Request) {
s.serveTailscaleUp(w, r)
case r.URL.Path == "/api/device-details-click" && r.Method == httpm.POST:
s.serveDeviceDetailsClick(w, r)
case r.URL.Path == "/api/update/progress" && r.Method == httpm.GET:
s.serveGetUpdateProgress(w, r)
default:
http.Error(w, "invalid endpoint or method", http.StatusNotFound)
}
@@ -620,6 +622,11 @@ func (s *Server) serveAPI(w http.ResponseWriter, r *http.Request) {
newHandler[noBodyData](s, w, r, alwaysAllowed).
handle(s.proxyRequestToLocalAPI)
return
case path == "/update/progress" && r.Method == httpm.GET:
s.serveGetUpdateProgress(w, r)
newHandler[noBodyData](s, w, r, alwaysAllowed).
handle(s.serveGetUpdateProgress)
return
}
http.Error(w, "invalid endpoint", http.StatusNotFound)
}
@@ -1224,6 +1231,21 @@ func (s *Server) serveDeviceDetailsClick(w http.ResponseWriter, r *http.Request)
io.WriteString(w, "{}")
}
// serveGetUpdateProgress proxies the localapi endpoint that gets the progress
// of an in-progress Tailscale update, bypassing the standard session check.
// This is because when Tailscale restarts as part of the update, the session
// is invalidated and the frontend can't use the standard localapi proxy for
// this endpoint, so it never realizes that the update is finished.
func (s *Server) serveGetUpdateProgress(w http.ResponseWriter, r *http.Request) {
ups, err := s.lc.GetUpdateProgress(r.Context())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(ups)
}
// proxyRequestToLocalAPI proxies the web API request to the localapi.
//
// The web API request path is expected to exactly match a localapi path,