119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
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.1–0.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}`))
|
||
}
|