feat: workspace endpoint, template/ssh-key dropdowns, noVNC console integration
This commit is contained in:
@@ -33,6 +33,7 @@ import (
|
||||
"proxui/backend/internal/sshkey"
|
||||
"proxui/backend/internal/template"
|
||||
"proxui/backend/internal/vm"
|
||||
"proxui/backend/internal/workspace"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -108,6 +109,7 @@ func main() {
|
||||
cfg.OperatorToken,
|
||||
)
|
||||
auditHandler := audit.NewHandler(audit.NewSQLRepository(db))
|
||||
workspaceHandler := workspace.NewHandler(workspace.NewRepository(db))
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -120,6 +122,10 @@ func main() {
|
||||
_ = json.NewEncoder(w).Encode(principal)
|
||||
})
|
||||
mux.Handle("GET /me", authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(meHandler)))
|
||||
mux.Handle(
|
||||
"GET /me/workspace",
|
||||
authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(workspaceHandler.GetWorkspace))),
|
||||
)
|
||||
tenantMembershipHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tenantMembership, _ := membership.FromRequest(r)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -206,6 +212,10 @@ func main() {
|
||||
)
|
||||
}
|
||||
mux.Handle("GET /tenants/{tenantID}/audit", auditRoute(auditHandler.ListTenantAudit))
|
||||
mux.Handle(
|
||||
"GET /templates",
|
||||
authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(templateHandler.ListTemplates))),
|
||||
)
|
||||
templateManageChain := func(handler http.HandlerFunc) http.Handler {
|
||||
return operatorMiddleware.RequireOperator(authorizationMiddleware.Require(rbac.ActionClusterManage, handler))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
package workspace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"proxui/backend/internal/auth"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *sql.DB) Repository {
|
||||
return Repository{db: db}
|
||||
}
|
||||
|
||||
func (r Repository) GetWorkspace(ctx context.Context, profileID string) (Workspace, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
select
|
||||
t.id::text,
|
||||
t.name,
|
||||
t.slug,
|
||||
m.role::text,
|
||||
coalesce(p.id::text, '') as project_id,
|
||||
coalesce(p.name, '') as project_name
|
||||
from public.tenants t
|
||||
join public.memberships m
|
||||
on m.tenant_id = t.id
|
||||
and m.profile_id = $1
|
||||
left join public.projects p
|
||||
on p.tenant_id = t.id
|
||||
where t.status = 'active'
|
||||
order by t.name asc, p.name asc
|
||||
`, profileID)
|
||||
if err != nil {
|
||||
return Workspace{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tenantMap := make(map[string]*Tenant)
|
||||
tenantOrder := []string{}
|
||||
|
||||
for rows.Next() {
|
||||
var tenantID, tenantName, tenantSlug, role, projectID, projectName string
|
||||
if err := rows.Scan(&tenantID, &tenantName, &tenantSlug, &role, &projectID, &projectName); err != nil {
|
||||
return Workspace{}, err
|
||||
}
|
||||
|
||||
tenant, exists := tenantMap[tenantID]
|
||||
if !exists {
|
||||
tenant = &Tenant{
|
||||
ID: tenantID,
|
||||
Name: tenantName,
|
||||
Slug: tenantSlug,
|
||||
Role: role,
|
||||
Projects: []Project{},
|
||||
}
|
||||
tenantMap[tenantID] = tenant
|
||||
tenantOrder = append(tenantOrder, tenantID)
|
||||
}
|
||||
|
||||
if projectID != "" {
|
||||
tenant.Projects = append(tenant.Projects, Project{
|
||||
ID: projectID,
|
||||
Name: projectName,
|
||||
TenantID: tenantID,
|
||||
})
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return Workspace{}, err
|
||||
}
|
||||
|
||||
tenants := make([]Tenant, 0, len(tenantOrder))
|
||||
for _, id := range tenantOrder {
|
||||
tenants = append(tenants, *tenantMap[id])
|
||||
}
|
||||
|
||||
return Workspace{Tenants: tenants}, nil
|
||||
}
|
||||
|
||||
type Workspace struct {
|
||||
Tenants []Tenant `json:"tenants"`
|
||||
}
|
||||
|
||||
type Tenant struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Role string `json:"role"`
|
||||
Projects []Project `json:"projects"`
|
||||
}
|
||||
|
||||
type Project struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
TenantID string `json:"tenant_id"`
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
repository Repository
|
||||
}
|
||||
|
||||
func NewHandler(repository Repository) Handler {
|
||||
return Handler{repository: repository}
|
||||
}
|
||||
|
||||
func (h Handler) GetWorkspace(w http.ResponseWriter, r *http.Request) {
|
||||
principal, ok := auth.PrincipalFromRequest(r)
|
||||
if !ok {
|
||||
writeError(w, http.StatusUnauthorized, "unauthorized")
|
||||
return
|
||||
}
|
||||
|
||||
workspace, err := h.repository.GetWorkspace(r.Context(), principal.Subject)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "workspace_failed")
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, workspace)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, body any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(body)
|
||||
}
|
||||
|
||||
func writeError(w http.ResponseWriter, status int, message string) {
|
||||
writeJSON(w, status, map[string]string{"error": message})
|
||||
}
|
||||
Reference in New Issue
Block a user