fix session with split screens

This commit is contained in:
2026-05-24 06:37:59 +00:00
parent 330cf01985
commit 45e18b5423
9 changed files with 760 additions and 224 deletions
+118
View File
@@ -0,0 +1,118 @@
package internals
import (
"encoding/json"
"net/http"
"strings"
"sync"
)
// credsMu guards concurrent reads/writes of appCreds.Workspaces from HTTP
// handlers. Credential fields (Username/Hash/etc.) are only written at
// startup via CLI flags that call os.Exit, so they don't need the lock.
var credsMu sync.Mutex
// WorkspacePaneLayout is a recursive pane tree.
// Leaf nodes own a PTY session; split nodes contain two children.
type WorkspacePaneLayout struct {
Type string `json:"type"` // "leaf" | "split"
ID string `json:"id,omitempty"` // PTY session hex ID (leaf only)
Dir string `json:"dir,omitempty"` // "h" | "v" (split only)
Ratio float64 `json:"ratio,omitempty"` // 0.10.9 (split only)
A *WorkspacePaneLayout `json:"a,omitempty"`
B *WorkspacePaneLayout `json:"b,omitempty"`
}
// WorkspaceTabLayout stores one tab's label and pane tree.
type WorkspaceTabLayout struct {
TabID string `json:"tab_id"`
Label string `json:"label"`
Root WorkspacePaneLayout `json:"root"`
}
// WorkspaceLayout is the full layout stored per workspace ID in gws-creds.json.
type WorkspaceLayout struct {
ID string `json:"id"`
Tabs []WorkspaceTabLayout `json:"tabs"`
}
// handleWorkspaceAPI dispatches GET / PUT / DELETE for /api/workspace/<id>.
// All methods require auth.
func handleWorkspaceAPI(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if !isAuthed(r) {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(`{"error":"unauthorized"}`))
return
}
id := strings.TrimPrefix(r.URL.Path, "/api/workspace/")
if !validID(id) {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"invalid id"}`))
return
}
switch r.Method {
case http.MethodGet:
workspaceGet(w, id)
case http.MethodPut:
workspacePut(w, r, id)
case http.MethodDelete:
workspaceDelete(w, id)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte(`{"error":"method not allowed"}`))
}
}
func workspaceGet(w http.ResponseWriter, id string) {
credsMu.Lock()
ws := appCreds.Workspaces[id]
credsMu.Unlock()
if ws == nil {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"error":"not found"}`))
return
}
json.NewEncoder(w).Encode(ws) //nolint:errcheck
}
func workspacePut(w http.ResponseWriter, r *http.Request, id string) {
var ws WorkspaceLayout
if err := json.NewDecoder(r.Body).Decode(&ws); err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":"bad json"}`))
return
}
ws.ID = id
credsMu.Lock()
if appCreds.Workspaces == nil {
appCreds.Workspaces = make(map[string]*WorkspaceLayout)
}
appCreds.Workspaces[id] = &ws
err := saveCreds(appCreds)
credsMu.Unlock()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":"save failed"}`))
return
}
w.Write([]byte(`{"ok":true}`))
}
func workspaceDelete(w http.ResponseWriter, id string) {
credsMu.Lock()
if appCreds.Workspaces != nil {
delete(appCreds.Workspaces, id)
}
err := saveCreds(appCreds)
credsMu.Unlock()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":"save failed"}`))
return
}
w.Write([]byte(`{"ok":true}`))
}