mirror of
https://github.com/tailscale/tailscale.git
synced 2025-01-05 23:07:44 +00:00
cmd/tailscale/cli: add logout and debug info to web
Fixes #7238 Signed-off-by: Jenny Zhang <jz@tailscale.com>
This commit is contained in:
parent
ea8b896c6c
commit
fe5558094c
@ -1286,6 +1286,28 @@ html {
|
||||
color: rgba(25, 34, 74, var(--text-opacity));
|
||||
}
|
||||
|
||||
.link-underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.link-underline:hover,
|
||||
.link-underline:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.link-muted {
|
||||
/* same as text-gray-500 */
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(112, 110, 109, var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.link-muted:hover,
|
||||
.link-muted:active {
|
||||
/* same as text-gray-500 */
|
||||
--tw-text-opacity: 1;
|
||||
color: rgba(68, 67, 66, var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.button {
|
||||
font-weight: 500;
|
||||
padding-top: 0.45rem;
|
||||
|
@ -60,6 +60,15 @@ type tmplData struct {
|
||||
LicensesURL string
|
||||
TUNMode bool
|
||||
IsSynology bool
|
||||
DSMVersion int // 6 or 7, if IsSynology=true
|
||||
IPNVersion string
|
||||
}
|
||||
|
||||
type postedData struct {
|
||||
AdvertiseRoutes string
|
||||
AdvertiseExitNode bool
|
||||
Reauthenticate bool
|
||||
ForceLogout bool
|
||||
}
|
||||
|
||||
var webCmd = &ffcli.Command{
|
||||
@ -133,7 +142,7 @@ func runWeb(ctx context.Context, args []string) error {
|
||||
Handler: http.HandlerFunc(webHandler),
|
||||
}
|
||||
|
||||
log.Printf("web server runNIng on: https://%s", server.Addr)
|
||||
log.Printf("web server running on: https://%s", server.Addr)
|
||||
return server.ListenAndServeTLS("", "")
|
||||
} else {
|
||||
log.Printf("web server running on: %s", urlOfListenAddr(webArgs.listen))
|
||||
@ -353,11 +362,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if r.Method == "POST" {
|
||||
defer r.Body.Close()
|
||||
var postData struct {
|
||||
AdvertiseRoutes string
|
||||
AdvertiseExitNode bool
|
||||
Reauthenticate bool
|
||||
}
|
||||
var postData postedData
|
||||
type mi map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&postData); err != nil {
|
||||
w.WriteHeader(400)
|
||||
@ -386,8 +391,15 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
log.Printf("tailscaleUp(reauth=%v) ...", postData.Reauthenticate)
|
||||
url, err := tailscaleUp(r.Context(), st, postData.Reauthenticate)
|
||||
var reauth, logout bool
|
||||
if postData.Reauthenticate {
|
||||
reauth = true
|
||||
}
|
||||
if postData.ForceLogout {
|
||||
logout = true
|
||||
}
|
||||
log.Printf("tailscaleUp(reauth=%v, logout=%v) ...", reauth, logout)
|
||||
url, err := tailscaleUp(r.Context(), st, postData)
|
||||
log.Printf("tailscaleUp = (URL %v, %v)", url != "", err)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@ -404,6 +416,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
profile := st.User[st.Self.UserID]
|
||||
deviceName := strings.Split(st.Self.DNSName, ".")[0]
|
||||
versionShort := strings.Split(st.Version, "-")[0]
|
||||
data := tmplData{
|
||||
SynologyUser: user,
|
||||
Profile: profile,
|
||||
@ -412,6 +425,8 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
LicensesURL: licensesURL(),
|
||||
TUNMode: st.TUN,
|
||||
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
||||
DSMVersion: distro.DSMVersion(),
|
||||
IPNVersion: versionShort,
|
||||
}
|
||||
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0")
|
||||
exitNodeRouteV6 := netip.MustParsePrefix("::/0")
|
||||
@ -437,10 +452,18 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
func tailscaleUp(ctx context.Context, st *ipnstate.Status, forceReauth bool) (authURL string, retErr error) {
|
||||
func tailscaleUp(ctx context.Context, st *ipnstate.Status, postData postedData) (authURL string, retErr error) {
|
||||
if postData.ForceLogout {
|
||||
if err := localClient.Logout(ctx); err != nil {
|
||||
return "", fmt.Errorf("Logout error: %w", err)
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
origAuthURL := st.AuthURL
|
||||
isRunning := st.BackendState == ipn.Running.String()
|
||||
|
||||
forceReauth := postData.Reauthenticate
|
||||
if !forceReauth {
|
||||
if origAuthURL != "" {
|
||||
return origAuthURL, nil
|
||||
|
@ -27,9 +27,13 @@
|
||||
</svg>
|
||||
<div class="flex items-center justify-end space-x-2 w-2/3">
|
||||
{{ with .Profile.LoginName }}
|
||||
<div class="text-right truncate leading-4">
|
||||
<div class="text-right w-full leading-4">
|
||||
<h4 class="truncate leading-normal">{{.}}</h4>
|
||||
<a href="#" class="text-xs text-gray-500 hover:text-gray-700 js-loginButton">Switch account</a>
|
||||
<div class="text-xs text-gray-500 text-right">
|
||||
<a href="#" class="hover:text-gray-700 js-loginButton">Switch account</a> | <a href="#"
|
||||
class="hover:text-gray-700 js-loginButton">Reauthenticate</a> | <a href="#"
|
||||
class="hover:text-gray-700 js-logoutButton">Logout</a>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="relative flex-shrink-0 w-8 h-8 rounded-full overflow-hidden">
|
||||
@ -44,7 +48,7 @@
|
||||
</header>
|
||||
{{ if .IP }}
|
||||
<div
|
||||
class="border border-gray-200 bg-gray-0 rounded-lg p-2 pl-3 pr-3 mb-8 width-full flex items-center justify-between">
|
||||
class="border border-gray-200 bg-gray-0 rounded-md p-2 pl-3 pr-3 width-full flex items-center justify-between">
|
||||
<div class="flex items-center min-width-0">
|
||||
<svg class="flex-shrink-0 text-gray-600 mr-3 ml-1" xmlns="http://www.w3.org/2000/svg" width="20" height="20"
|
||||
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||
@ -54,10 +58,21 @@
|
||||
<line x1="6" y1="6" x2="6.01" y2="6"></line>
|
||||
<line x1="6" y1="18" x2="6.01" y2="18"></line>
|
||||
</svg>
|
||||
<h4 class="font-semibold truncate mr-2">{{.DeviceName}}</h4>
|
||||
<div>
|
||||
<h4 class="font-semibold truncate mr-2">{{.DeviceName}}</h4>
|
||||
</div>
|
||||
</div>
|
||||
<h5>{{.IP}}</h5>
|
||||
</div>
|
||||
<p class="mt-1 ml-1 mb-6 text-xs text-gray-600">
|
||||
Debug info: Tailscale {{ .IPNVersion }}, tun={{.TUNMode}}{{ if .IsSynology }}, DSM{{ .DSMVersion}}
|
||||
{{if not .TUNMode}}
|
||||
(<a href="https://tailscale.com/kb/1152/synology-outbound/" class="link-underline text-gray-600" target="_blank"
|
||||
aria-label="Configure outbound synology traffic"
|
||||
rel="noopener noreferrer">outgoing access not configured</a>)
|
||||
{{end}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{ end }}
|
||||
{{ if or (eq .Status "NeedsLogin") (eq .Status "NoState") }}
|
||||
{{ if .IP }}
|
||||
@ -95,18 +110,6 @@
|
||||
{{end}}
|
||||
</a>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<a href="#" class="mb-4 link font-medium js-loginButton" target="_blank">Reauthenticate</a>
|
||||
</div>
|
||||
{{ if .IsSynology }}
|
||||
<div class="border border-gray-200 bg-orange-0 rounded-lg p-2 pl-3 pr-3 mb-8 width-full text-orange-800">
|
||||
Outgoing access {{ if .TUNMode }}enabled{{ else }}not configured{{ end }}.
|
||||
<nobr><a href="https://tailscale.com/kb/1152/synology-outbound/"
|
||||
class="font-medium link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Learn more →</a></nobr>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</main>
|
||||
<footer class="container max-w-lg mx-auto text-center">
|
||||
@ -118,7 +121,8 @@ let fetchingUrl = false;
|
||||
var data = {
|
||||
AdvertiseRoutes: "{{.AdvertiseRoutes}}",
|
||||
AdvertiseExitNode: advertiseExitNode,
|
||||
Reauthenticate: false
|
||||
Reauthenticate: false,
|
||||
ForceLogout: false
|
||||
};
|
||||
|
||||
function postData(e) {
|
||||
@ -159,17 +163,23 @@ function postData(e) {
|
||||
location.reload();
|
||||
}
|
||||
}).catch(err => {
|
||||
alert("Failed to log in: " + err.message);
|
||||
alert("Failed operation: " + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
Array.from(document.querySelectorAll(".js-loginButton")).forEach(el => {
|
||||
document.querySelectorAll(".js-loginButton").forEach(function (el){
|
||||
el.addEventListener("click", function(e) {
|
||||
data.Reauthenticate = true;
|
||||
postData(e);
|
||||
});
|
||||
})
|
||||
Array.from(document.querySelectorAll(".js-advertiseExitNode")).forEach(el => {
|
||||
document.querySelectorAll(".js-logoutButton").forEach(function(el) {
|
||||
el.addEventListener("click", function (e) {
|
||||
data.ForceLogout = true;
|
||||
postData(e);
|
||||
});
|
||||
})
|
||||
document.querySelectorAll(".js-advertiseExitNode").forEach(function (el) {
|
||||
el.addEventListener("click", function(e) {
|
||||
data.AdvertiseExitNode = !advertiseExitNode;
|
||||
postData(e);
|
||||
|
Loading…
x
Reference in New Issue
Block a user