mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-16 03:31:39 +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));
|
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 {
|
.button {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding-top: 0.45rem;
|
padding-top: 0.45rem;
|
||||||
|
@ -60,6 +60,15 @@ type tmplData struct {
|
|||||||
LicensesURL string
|
LicensesURL string
|
||||||
TUNMode bool
|
TUNMode bool
|
||||||
IsSynology 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{
|
var webCmd = &ffcli.Command{
|
||||||
@ -133,7 +142,7 @@ func runWeb(ctx context.Context, args []string) error {
|
|||||||
Handler: http.HandlerFunc(webHandler),
|
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("", "")
|
return server.ListenAndServeTLS("", "")
|
||||||
} else {
|
} else {
|
||||||
log.Printf("web server running on: %s", urlOfListenAddr(webArgs.listen))
|
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" {
|
if r.Method == "POST" {
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
var postData struct {
|
var postData postedData
|
||||||
AdvertiseRoutes string
|
|
||||||
AdvertiseExitNode bool
|
|
||||||
Reauthenticate bool
|
|
||||||
}
|
|
||||||
type mi map[string]any
|
type mi map[string]any
|
||||||
if err := json.NewDecoder(r.Body).Decode(&postData); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&postData); err != nil {
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
@ -386,8 +391,15 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
log.Printf("tailscaleUp(reauth=%v) ...", postData.Reauthenticate)
|
var reauth, logout bool
|
||||||
url, err := tailscaleUp(r.Context(), st, postData.Reauthenticate)
|
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)
|
log.Printf("tailscaleUp = (URL %v, %v)", url != "", err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
@ -404,6 +416,7 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
profile := st.User[st.Self.UserID]
|
profile := st.User[st.Self.UserID]
|
||||||
deviceName := strings.Split(st.Self.DNSName, ".")[0]
|
deviceName := strings.Split(st.Self.DNSName, ".")[0]
|
||||||
|
versionShort := strings.Split(st.Version, "-")[0]
|
||||||
data := tmplData{
|
data := tmplData{
|
||||||
SynologyUser: user,
|
SynologyUser: user,
|
||||||
Profile: profile,
|
Profile: profile,
|
||||||
@ -412,6 +425,8 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
LicensesURL: licensesURL(),
|
LicensesURL: licensesURL(),
|
||||||
TUNMode: st.TUN,
|
TUNMode: st.TUN,
|
||||||
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
IsSynology: distro.Get() == distro.Synology || envknob.Bool("TS_FAKE_SYNOLOGY"),
|
||||||
|
DSMVersion: distro.DSMVersion(),
|
||||||
|
IPNVersion: versionShort,
|
||||||
}
|
}
|
||||||
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0")
|
exitNodeRouteV4 := netip.MustParsePrefix("0.0.0.0/0")
|
||||||
exitNodeRouteV6 := netip.MustParsePrefix("::/0")
|
exitNodeRouteV6 := netip.MustParsePrefix("::/0")
|
||||||
@ -437,10 +452,18 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.Write(buf.Bytes())
|
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
|
origAuthURL := st.AuthURL
|
||||||
isRunning := st.BackendState == ipn.Running.String()
|
isRunning := st.BackendState == ipn.Running.String()
|
||||||
|
|
||||||
|
forceReauth := postData.Reauthenticate
|
||||||
if !forceReauth {
|
if !forceReauth {
|
||||||
if origAuthURL != "" {
|
if origAuthURL != "" {
|
||||||
return origAuthURL, nil
|
return origAuthURL, nil
|
||||||
|
@ -27,9 +27,13 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<div class="flex items-center justify-end space-x-2 w-2/3">
|
<div class="flex items-center justify-end space-x-2 w-2/3">
|
||||||
{{ with .Profile.LoginName }}
|
{{ with .Profile.LoginName }}
|
||||||
<div class="text-right truncate leading-4">
|
<div class="text-right w-full leading-4">
|
||||||
<h4 class="truncate leading-normal">{{.}}</h4>
|
<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>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="relative flex-shrink-0 w-8 h-8 rounded-full overflow-hidden">
|
<div class="relative flex-shrink-0 w-8 h-8 rounded-full overflow-hidden">
|
||||||
@ -44,7 +48,7 @@
|
|||||||
</header>
|
</header>
|
||||||
{{ if .IP }}
|
{{ if .IP }}
|
||||||
<div
|
<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">
|
<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"
|
<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"
|
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="6" x2="6.01" y2="6"></line>
|
||||||
<line x1="6" y1="18" x2="6.01" y2="18"></line>
|
<line x1="6" y1="18" x2="6.01" y2="18"></line>
|
||||||
</svg>
|
</svg>
|
||||||
<h4 class="font-semibold truncate mr-2">{{.DeviceName}}</h4>
|
<div>
|
||||||
|
<h4 class="font-semibold truncate mr-2">{{.DeviceName}}</h4>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h5>{{.IP}}</h5>
|
<h5>{{.IP}}</h5>
|
||||||
</div>
|
</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 }}
|
{{ end }}
|
||||||
{{ if or (eq .Status "NeedsLogin") (eq .Status "NoState") }}
|
{{ if or (eq .Status "NeedsLogin") (eq .Status "NoState") }}
|
||||||
{{ if .IP }}
|
{{ if .IP }}
|
||||||
@ -95,18 +110,6 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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 }}
|
{{ end }}
|
||||||
</main>
|
</main>
|
||||||
<footer class="container max-w-lg mx-auto text-center">
|
<footer class="container max-w-lg mx-auto text-center">
|
||||||
@ -118,7 +121,8 @@ let fetchingUrl = false;
|
|||||||
var data = {
|
var data = {
|
||||||
AdvertiseRoutes: "{{.AdvertiseRoutes}}",
|
AdvertiseRoutes: "{{.AdvertiseRoutes}}",
|
||||||
AdvertiseExitNode: advertiseExitNode,
|
AdvertiseExitNode: advertiseExitNode,
|
||||||
Reauthenticate: false
|
Reauthenticate: false,
|
||||||
|
ForceLogout: false
|
||||||
};
|
};
|
||||||
|
|
||||||
function postData(e) {
|
function postData(e) {
|
||||||
@ -159,17 +163,23 @@ function postData(e) {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
}).catch(err => {
|
}).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) {
|
el.addEventListener("click", function(e) {
|
||||||
data.Reauthenticate = true;
|
data.Reauthenticate = true;
|
||||||
postData(e);
|
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) {
|
el.addEventListener("click", function(e) {
|
||||||
data.AdvertiseExitNode = !advertiseExitNode;
|
data.AdvertiseExitNode = !advertiseExitNode;
|
||||||
postData(e);
|
postData(e);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user