133 lines
3.1 KiB
Go
133 lines
3.1 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"chat-agent-system/internal/config"
|
|
"chat-agent-system/internal/workspace"
|
|
)
|
|
|
|
func (s *Server) handleAdminWorkspaces(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := s.requireAdmin(w, r); !ok {
|
|
return
|
|
}
|
|
s.serveAdminWorkspaces(w, r)
|
|
}
|
|
|
|
func (s *Server) handleAdminWorkspaceCleanup(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := s.requireAdmin(w, r); !ok {
|
|
return
|
|
}
|
|
s.serveAdminWorkspaceCleanup(w, r)
|
|
}
|
|
|
|
func (s *Server) workspaceRegistry() *workspace.FileRegistry {
|
|
if s.wsReg != nil {
|
|
return s.wsReg
|
|
}
|
|
if s.cfg == nil || s.cfg.DataDir == "" {
|
|
return nil
|
|
}
|
|
s.wsReg = workspace.NewFileRegistry(s.cfg.DataDir)
|
|
return s.wsReg
|
|
}
|
|
|
|
func (s *Server) serveAdminWorkspaces(w http.ResponseWriter, r *http.Request) {
|
|
reg := s.workspaceRegistry()
|
|
if reg == nil {
|
|
writeSafeError(w, r, http.StatusServiceUnavailable, "workspace registry unavailable")
|
|
return
|
|
}
|
|
if err := reg.Discover(); err != nil {
|
|
writeDomainError(w, r, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
list, err := reg.List()
|
|
if err != nil {
|
|
writeDomainError(w, r, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
var bytes int64
|
|
for _, rec := range list {
|
|
bytes += rec.Bytes
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, map[string]any{
|
|
"workspaces": list,
|
|
"stats": map[string]any{
|
|
"count": len(list),
|
|
"bytes": bytes,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (s *Server) serveAdminWorkspaceCleanup(w http.ResponseWriter, r *http.Request) {
|
|
reg := s.workspaceRegistry()
|
|
if reg == nil {
|
|
writeSafeError(w, r, http.StatusServiceUnavailable, "workspace registry unavailable")
|
|
return
|
|
}
|
|
id := r.PathValue("id")
|
|
var body struct {
|
|
Frozen *workspace.FrozenManifest `json:"frozen"`
|
|
Receipts []workspace.RetentionReceipt `json:"receipts"`
|
|
}
|
|
if r.Body != nil {
|
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
|
}
|
|
if body.Frozen != nil {
|
|
if err := reg.SetRetention(id, *body.Frozen, body.Receipts); err != nil {
|
|
writeDomainError(w, r, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
}
|
|
cap := s.wsCap()
|
|
if cap == nil && s.cfg != nil {
|
|
cap = workspace.NewCapability(s.cfg.DataDir, nil)
|
|
}
|
|
if err := reg.CleanupOne(r.Context(), id, cap); err != nil {
|
|
status := http.StatusConflict
|
|
if err == workspace.ErrWorkDirNotFound {
|
|
status = http.StatusNotFound
|
|
}
|
|
writeDomainError(w, r, status, err)
|
|
return
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, map[string]any{"deleted": id})
|
|
}
|
|
|
|
func workspaceRegistryFor(cfg *config.Config) *workspace.FileRegistry {
|
|
if cfg == nil || cfg.DataDir == "" {
|
|
return nil
|
|
}
|
|
return workspace.NewFileRegistry(cfg.DataDir)
|
|
}
|
|
|
|
func (s *Server) startWorkspaceGC() {
|
|
reg := s.workspaceRegistry()
|
|
stopCh, ok := s.wsGCLoop.start()
|
|
if reg == nil || s.cfg == nil || !ok {
|
|
return
|
|
}
|
|
ttl := s.cfg.WorkspaceTTL
|
|
quota := s.cfg.WorkspaceQuotaBytes
|
|
go func() {
|
|
ticker := time.NewTicker(time.Hour)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-stopCh:
|
|
return
|
|
case <-ticker.C:
|
|
cap := s.wsCap()
|
|
if cap == nil {
|
|
cap = workspace.NewCapability(s.cfg.DataDir, nil)
|
|
}
|
|
_, _ = reg.CollectGarbage(context.Background(), time.Now(), ttl, quota, cap)
|
|
}
|
|
}
|
|
}()
|
|
}
|