feat: complete MVP epics E7-E10 (provisioning, console proxy, audit, frontend)

This commit is contained in:
Philipp
2026-06-12 10:45:13 +02:00
parent 137c13fa98
commit ac30a19960
33 changed files with 3625 additions and 263 deletions
+93 -1
View File
@@ -19,14 +19,19 @@ import (
"github.com/hibiken/asynq"
_ "github.com/jackc/pgx/v5/stdlib"
"proxui/backend/internal/audit"
"proxui/backend/internal/auth"
"proxui/backend/internal/authorization"
"proxui/backend/internal/clusteradmin"
"proxui/backend/internal/console"
"proxui/backend/internal/consoleadapter"
"proxui/backend/internal/membership"
"proxui/backend/internal/operator"
"proxui/backend/internal/profile"
"proxui/backend/internal/queue"
"proxui/backend/internal/rbac"
"proxui/backend/internal/sshkey"
"proxui/backend/internal/template"
"proxui/backend/internal/vm"
)
@@ -73,15 +78,36 @@ func main() {
clusterRepository := cluster.NewRepository(db, tokenCipher)
clusterAdminHandler := clusteradmin.NewHandler(clusterRepository)
operatorMiddleware := operator.NewMiddleware(cfg.OperatorToken)
sshKeyHandler := sshkey.NewHandler(sshkey.NewSQLRepository(db))
sshKeyResolver := sshkey.NewPublicKeyResolver(sshkey.NewSQLRepository(db))
templateHandler := template.NewHandler(template.NewSQLRepository(db))
templateResolver := template.NewProvisionResolver(template.NewSQLRepository(db))
vmRepo := vm.NewSQLRepository(db)
vmHandler := vm.NewHandler(
vm.NewSQLRepository(db),
vmRepo,
vm.WithPower(vm.PowerDependencies{
Clusters: clusterRepository,
ClientFactory: vm.DefaultPowerClientFactory,
Tasks: taskEnqueuer,
Audit: vm.NewSQLPowerAuditWriter(db),
}),
vm.WithProvision(vm.ProvisionDependencies{
ClusterRepo: clusterRepository,
TemplateRepo: templateResolver,
ClientFactory: vm.DefaultProvisionClientFactory,
Tasks: taskEnqueuer,
Audit: vm.NewSQLProvisionAuditWriter(db),
SSHKeyLookup: sshKeyResolver,
}),
)
consoleHandler := console.NewHandler(
clusterRepository,
consoleadapter.NewVMRepository(vmRepo),
console.DefaultProxyClientFactory,
cfg.OperatorToken,
)
auditHandler := audit.NewHandler(audit.NewSQLRepository(db))
mux := http.NewServeMux()
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
@@ -121,6 +147,72 @@ func main() {
mux.Handle("POST /vms/{vmID}/start", authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(vmHandler.StartVM))))
mux.Handle("POST /vms/{vmID}/stop", authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(vmHandler.StopVM))))
mux.Handle("POST /vms/{vmID}/reboot", authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(vmHandler.RebootVM))))
mux.Handle(
"POST /projects/{projectID}/vms",
authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(vmHandler.CreateVM))),
)
mux.Handle(
"DELETE /vms/{vmID}",
authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(vmHandler.DeleteVM))),
)
mux.Handle(
"POST /vms/{vmID}/console",
authMiddleware.RequireAuth(profileMiddleware.EnsureProfile(http.HandlerFunc(consoleHandler.CreateConsoleTicket))),
)
mux.Handle(
"GET /tenants/{tenantID}/ssh-keys",
authMiddleware.RequireAuth(
profileMiddleware.EnsureProfile(
membershipMiddleware.RequireTenantMembership(
pathTenantID,
authorizationMiddleware.Require(rbac.ActionSSHKeyRead, http.HandlerFunc(sshKeyHandler.ListTenantKeys)),
),
),
),
)
mux.Handle(
"POST /tenants/{tenantID}/ssh-keys",
authMiddleware.RequireAuth(
profileMiddleware.EnsureProfile(
membershipMiddleware.RequireTenantMembership(
pathTenantID,
authorizationMiddleware.Require(rbac.ActionSSHKeyManage, http.HandlerFunc(sshKeyHandler.CreateKey)),
),
),
),
)
mux.Handle(
"GET /ssh-keys/{keyID}",
authMiddleware.RequireAuth(
profileMiddleware.EnsureProfile(http.HandlerFunc(sshKeyHandler.GetKey)),
),
)
mux.Handle(
"DELETE /ssh-keys/{keyID}",
authMiddleware.RequireAuth(
profileMiddleware.EnsureProfile(
authorizationMiddleware.Require(rbac.ActionSSHKeyManage, http.HandlerFunc(sshKeyHandler.DeleteKey)),
),
),
)
auditRoute := func(handler http.HandlerFunc) http.Handler {
return authMiddleware.RequireAuth(
profileMiddleware.EnsureProfile(
membershipMiddleware.RequireTenantMembership(
pathTenantID,
authorizationMiddleware.Require(rbac.ActionAuditRead, handler),
),
),
)
}
mux.Handle("GET /tenants/{tenantID}/audit", auditRoute(auditHandler.ListTenantAudit))
templateManageChain := func(handler http.HandlerFunc) http.Handler {
return operatorMiddleware.RequireOperator(authorizationMiddleware.Require(rbac.ActionClusterManage, handler))
}
mux.Handle("GET /internal/templates", templateManageChain(templateHandler.ListTemplates))
mux.Handle("POST /internal/templates", templateManageChain(templateHandler.CreateTemplate))
mux.Handle("PUT /internal/templates/{templateID}", templateManageChain(templateHandler.UpdateTemplate))
mux.Handle("DELETE /internal/templates/{templateID}", templateManageChain(templateHandler.DeleteTemplate))
clusterManageChain := func(handler http.HandlerFunc) http.Handler {
return operatorMiddleware.RequireOperator(authorizationMiddleware.Require(rbac.ActionClusterManage, handler))
}