aboutsummaryrefslogtreecommitdiff
path: root/weed/admin/handlers
diff options
context:
space:
mode:
authorChris Lu <chrislusf@users.noreply.github.com>2025-07-12 01:13:11 -0700
committerGitHub <noreply@github.com>2025-07-12 01:13:11 -0700
commit687a6a6c1de0fb67b51ec9bfd1781a6c255ff695 (patch)
tree3ee2890c890e67a170cec2692425528aa9cd795f /weed/admin/handlers
parent49d43003e1f5063c57cd1b122469c0cb68d0cd79 (diff)
downloadseaweedfs-687a6a6c1de0fb67b51ec9bfd1781a6c255ff695.tar.xz
seaweedfs-687a6a6c1de0fb67b51ec9bfd1781a6c255ff695.zip
Admin UI: Add policies (#6968)
* add policies to UI, accessing filer directly * view, edit policies * add back buttons for "users" page * remove unused * fix ui dark mode when modal is closed * bucket view details button * fix browser buttons * filer action button works * clean up masters page * fix volume servers action buttons * fix collections page action button * fix properties page * more obvious * fix directory creation file mode * Update file_browser_handlers.go * directory permission
Diffstat (limited to 'weed/admin/handlers')
-rw-r--r--weed/admin/handlers/admin_handlers.go27
-rw-r--r--weed/admin/handlers/file_browser_handlers.go15
-rw-r--r--weed/admin/handlers/maintenance_handlers.go233
-rw-r--r--weed/admin/handlers/policy_handlers.go273
4 files changed, 419 insertions, 129 deletions
diff --git a/weed/admin/handlers/admin_handlers.go b/weed/admin/handlers/admin_handlers.go
index dc7905bc1..76a123a4f 100644
--- a/weed/admin/handlers/admin_handlers.go
+++ b/weed/admin/handlers/admin_handlers.go
@@ -17,6 +17,7 @@ type AdminHandlers struct {
clusterHandlers *ClusterHandlers
fileBrowserHandlers *FileBrowserHandlers
userHandlers *UserHandlers
+ policyHandlers *PolicyHandlers
maintenanceHandlers *MaintenanceHandlers
mqHandlers *MessageQueueHandlers
}
@@ -27,6 +28,7 @@ func NewAdminHandlers(adminServer *dash.AdminServer) *AdminHandlers {
clusterHandlers := NewClusterHandlers(adminServer)
fileBrowserHandlers := NewFileBrowserHandlers(adminServer)
userHandlers := NewUserHandlers(adminServer)
+ policyHandlers := NewPolicyHandlers(adminServer)
maintenanceHandlers := NewMaintenanceHandlers(adminServer)
mqHandlers := NewMessageQueueHandlers(adminServer)
return &AdminHandlers{
@@ -35,6 +37,7 @@ func NewAdminHandlers(adminServer *dash.AdminServer) *AdminHandlers {
clusterHandlers: clusterHandlers,
fileBrowserHandlers: fileBrowserHandlers,
userHandlers: userHandlers,
+ policyHandlers: policyHandlers,
maintenanceHandlers: maintenanceHandlers,
mqHandlers: mqHandlers,
}
@@ -63,6 +66,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, username,
protected.GET("/object-store/buckets", h.ShowS3Buckets)
protected.GET("/object-store/buckets/:bucket", h.ShowBucketDetails)
protected.GET("/object-store/users", h.userHandlers.ShowObjectStoreUsers)
+ protected.GET("/object-store/policies", h.policyHandlers.ShowPolicies)
// File browser routes
protected.GET("/files", h.fileBrowserHandlers.ShowFileBrowser)
@@ -121,6 +125,17 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, username,
usersApi.PUT("/:username/policies", h.userHandlers.UpdateUserPolicies)
}
+ // Object Store Policy management API routes
+ objectStorePoliciesApi := api.Group("/object-store/policies")
+ {
+ objectStorePoliciesApi.GET("", h.policyHandlers.GetPolicies)
+ objectStorePoliciesApi.POST("", h.policyHandlers.CreatePolicy)
+ objectStorePoliciesApi.GET("/:name", h.policyHandlers.GetPolicy)
+ objectStorePoliciesApi.PUT("/:name", h.policyHandlers.UpdatePolicy)
+ objectStorePoliciesApi.DELETE("/:name", h.policyHandlers.DeletePolicy)
+ objectStorePoliciesApi.POST("/validate", h.policyHandlers.ValidatePolicy)
+ }
+
// File management API routes
filesApi := api.Group("/files")
{
@@ -171,6 +186,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, username,
r.GET("/object-store/buckets", h.ShowS3Buckets)
r.GET("/object-store/buckets/:bucket", h.ShowBucketDetails)
r.GET("/object-store/users", h.userHandlers.ShowObjectStoreUsers)
+ r.GET("/object-store/policies", h.policyHandlers.ShowPolicies)
// File browser routes
r.GET("/files", h.fileBrowserHandlers.ShowFileBrowser)
@@ -229,6 +245,17 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, username,
usersApi.PUT("/:username/policies", h.userHandlers.UpdateUserPolicies)
}
+ // Object Store Policy management API routes
+ objectStorePoliciesApi := api.Group("/object-store/policies")
+ {
+ objectStorePoliciesApi.GET("", h.policyHandlers.GetPolicies)
+ objectStorePoliciesApi.POST("", h.policyHandlers.CreatePolicy)
+ objectStorePoliciesApi.GET("/:name", h.policyHandlers.GetPolicy)
+ objectStorePoliciesApi.PUT("/:name", h.policyHandlers.UpdatePolicy)
+ objectStorePoliciesApi.DELETE("/:name", h.policyHandlers.DeletePolicy)
+ objectStorePoliciesApi.POST("/validate", h.policyHandlers.ValidatePolicy)
+ }
+
// File management API routes
filesApi := api.Group("/files")
{
diff --git a/weed/admin/handlers/file_browser_handlers.go b/weed/admin/handlers/file_browser_handlers.go
index 97621192e..c8e117041 100644
--- a/weed/admin/handlers/file_browser_handlers.go
+++ b/weed/admin/handlers/file_browser_handlers.go
@@ -8,6 +8,7 @@ import (
"mime/multipart"
"net"
"net/http"
+ "os"
"path/filepath"
"strconv"
"strings"
@@ -190,7 +191,7 @@ func (h *FileBrowserHandlers) CreateFolder(c *gin.Context) {
Name: filepath.Base(fullPath),
IsDirectory: true,
Attributes: &filer_pb.FuseAttributes{
- FileMode: uint32(0755 | (1 << 31)), // Directory mode
+ FileMode: uint32(0755 | os.ModeDir), // Directory mode
Uid: filer_pb.OS_UID,
Gid: filer_pb.OS_GID,
Crtime: time.Now().Unix(),
@@ -656,8 +657,9 @@ func (h *FileBrowserHandlers) GetFileProperties(c *gin.Context) {
properties["created_timestamp"] = entry.Attributes.Crtime
}
- properties["file_mode"] = fmt.Sprintf("%o", entry.Attributes.FileMode)
- properties["file_mode_formatted"] = h.formatFileMode(entry.Attributes.FileMode)
+ properties["file_mode"] = dash.FormatFileMode(entry.Attributes.FileMode)
+ properties["file_mode_formatted"] = dash.FormatFileMode(entry.Attributes.FileMode)
+ properties["file_mode_octal"] = fmt.Sprintf("%o", entry.Attributes.FileMode)
properties["uid"] = entry.Attributes.Uid
properties["gid"] = entry.Attributes.Gid
properties["ttl_seconds"] = entry.Attributes.TtlSec
@@ -725,13 +727,6 @@ func (h *FileBrowserHandlers) formatBytes(bytes int64) string {
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
-// Helper function to format file mode
-func (h *FileBrowserHandlers) formatFileMode(mode uint32) string {
- // Convert to octal and format as rwx permissions
- perm := mode & 0777
- return fmt.Sprintf("%03o", perm)
-}
-
// Helper function to determine MIME type from filename
func (h *FileBrowserHandlers) determineMimeType(filename string) string {
ext := strings.ToLower(filepath.Ext(filename))
diff --git a/weed/admin/handlers/maintenance_handlers.go b/weed/admin/handlers/maintenance_handlers.go
index 954874c14..4b1f91387 100644
--- a/weed/admin/handlers/maintenance_handlers.go
+++ b/weed/admin/handlers/maintenance_handlers.go
@@ -11,9 +11,6 @@ import (
"github.com/seaweedfs/seaweedfs/weed/admin/view/components"
"github.com/seaweedfs/seaweedfs/weed/admin/view/layout"
"github.com/seaweedfs/seaweedfs/weed/worker/tasks"
- "github.com/seaweedfs/seaweedfs/weed/worker/tasks/balance"
- "github.com/seaweedfs/seaweedfs/weed/worker/tasks/erasure_coding"
- "github.com/seaweedfs/seaweedfs/weed/worker/tasks/vacuum"
"github.com/seaweedfs/seaweedfs/weed/worker/types"
)
@@ -114,59 +111,60 @@ func (h *MaintenanceHandlers) ShowTaskConfig(c *gin.Context) {
return
}
- // Try to get templ UI provider first
- templUIProvider := getTemplUIProvider(taskType)
+ // Try to get templ UI provider first - temporarily disabled
+ // templUIProvider := getTemplUIProvider(taskType)
var configSections []components.ConfigSectionData
- if templUIProvider != nil {
- // Use the new templ-based UI provider
- currentConfig := templUIProvider.GetCurrentConfig()
- sections, err := templUIProvider.RenderConfigSections(currentConfig)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render configuration sections: " + err.Error()})
- return
- }
- configSections = sections
- } else {
- // Fallback to basic configuration for providers that haven't been migrated yet
- configSections = []components.ConfigSectionData{
- {
- Title: "Configuration Settings",
- Icon: "fas fa-cogs",
- Description: "Configure task detection and scheduling parameters",
- Fields: []interface{}{
- components.CheckboxFieldData{
- FormFieldData: components.FormFieldData{
- Name: "enabled",
- Label: "Enable Task",
- Description: "Whether this task type should be enabled",
- },
- Checked: true,
+ // Temporarily disabled templ UI provider
+ // if templUIProvider != nil {
+ // // Use the new templ-based UI provider
+ // currentConfig := templUIProvider.GetCurrentConfig()
+ // sections, err := templUIProvider.RenderConfigSections(currentConfig)
+ // if err != nil {
+ // c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render configuration sections: " + err.Error()})
+ // return
+ // }
+ // configSections = sections
+ // } else {
+ // Fallback to basic configuration for providers that haven't been migrated yet
+ configSections = []components.ConfigSectionData{
+ {
+ Title: "Configuration Settings",
+ Icon: "fas fa-cogs",
+ Description: "Configure task detection and scheduling parameters",
+ Fields: []interface{}{
+ components.CheckboxFieldData{
+ FormFieldData: components.FormFieldData{
+ Name: "enabled",
+ Label: "Enable Task",
+ Description: "Whether this task type should be enabled",
},
- components.NumberFieldData{
- FormFieldData: components.FormFieldData{
- Name: "max_concurrent",
- Label: "Max Concurrent Tasks",
- Description: "Maximum number of concurrent tasks",
- Required: true,
- },
- Value: 2,
- Step: "1",
- Min: floatPtr(1),
+ Checked: true,
+ },
+ components.NumberFieldData{
+ FormFieldData: components.FormFieldData{
+ Name: "max_concurrent",
+ Label: "Max Concurrent Tasks",
+ Description: "Maximum number of concurrent tasks",
+ Required: true,
},
- components.DurationFieldData{
- FormFieldData: components.FormFieldData{
- Name: "scan_interval",
- Label: "Scan Interval",
- Description: "How often to scan for tasks",
- Required: true,
- },
- Value: "30m",
+ Value: 2,
+ Step: "1",
+ Min: floatPtr(1),
+ },
+ components.DurationFieldData{
+ FormFieldData: components.FormFieldData{
+ Name: "scan_interval",
+ Label: "Scan Interval",
+ Description: "How often to scan for tasks",
+ Required: true,
},
+ Value: "30m",
},
},
- }
+ },
}
+ // } // End of disabled templ UI provider else block
// Create task configuration data using templ components
configData := &app.TaskConfigTemplData{
@@ -199,8 +197,8 @@ func (h *MaintenanceHandlers) UpdateTaskConfig(c *gin.Context) {
return
}
- // Try to get templ UI provider first
- templUIProvider := getTemplUIProvider(taskType)
+ // Try to get templ UI provider first - temporarily disabled
+ // templUIProvider := getTemplUIProvider(taskType)
// Parse form data
err := c.Request.ParseForm()
@@ -217,52 +215,53 @@ func (h *MaintenanceHandlers) UpdateTaskConfig(c *gin.Context) {
var config interface{}
- if templUIProvider != nil {
- // Use the new templ-based UI provider
- config, err = templUIProvider.ParseConfigForm(formData)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse configuration: " + err.Error()})
- return
- }
+ // Temporarily disabled templ UI provider
+ // if templUIProvider != nil {
+ // // Use the new templ-based UI provider
+ // config, err = templUIProvider.ParseConfigForm(formData)
+ // if err != nil {
+ // c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse configuration: " + err.Error()})
+ // return
+ // }
+ // // Apply configuration using templ provider
+ // err = templUIProvider.ApplyConfig(config)
+ // if err != nil {
+ // c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to apply configuration: " + err.Error()})
+ // return
+ // }
+ // } else {
+ // Fallback to old UI provider for tasks that haven't been migrated yet
+ // Fallback to old UI provider for tasks that haven't been migrated yet
+ uiRegistry := tasks.GetGlobalUIRegistry()
+ typesRegistry := tasks.GetGlobalTypesRegistry()
- // Apply configuration using templ provider
- err = templUIProvider.ApplyConfig(config)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to apply configuration: " + err.Error()})
- return
- }
- } else {
- // Fallback to old UI provider for tasks that haven't been migrated yet
- uiRegistry := tasks.GetGlobalUIRegistry()
- typesRegistry := tasks.GetGlobalTypesRegistry()
-
- var provider types.TaskUIProvider
- for workerTaskType := range typesRegistry.GetAllDetectors() {
- if string(workerTaskType) == string(taskType) {
- provider = uiRegistry.GetProvider(workerTaskType)
- break
- }
+ var provider types.TaskUIProvider
+ for workerTaskType := range typesRegistry.GetAllDetectors() {
+ if string(workerTaskType) == string(taskType) {
+ provider = uiRegistry.GetProvider(workerTaskType)
+ break
}
+ }
- if provider == nil {
- c.JSON(http.StatusNotFound, gin.H{"error": "UI provider not found for task type"})
- return
- }
+ if provider == nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "UI provider not found for task type"})
+ return
+ }
- // Parse configuration from form using old provider
- config, err = provider.ParseConfigForm(formData)
- if err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse configuration: " + err.Error()})
- return
- }
+ // Parse configuration from form using old provider
+ config, err = provider.ParseConfigForm(formData)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse configuration: " + err.Error()})
+ return
+ }
- // Apply configuration using old provider
- err = provider.ApplyConfig(config)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to apply configuration: " + err.Error()})
- return
- }
+ // Apply configuration using old provider
+ err = provider.ApplyConfig(config)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to apply configuration: " + err.Error()})
+ return
}
+ // } // End of disabled templ UI provider else block
// Redirect back to task configuration page
c.Redirect(http.StatusSeeOther, "/maintenance/config/"+taskTypeName)
@@ -350,39 +349,35 @@ func floatPtr(f float64) *float64 {
return &f
}
-// Global templ UI registry
-var globalTemplUIRegistry *types.UITemplRegistry
+// Global templ UI registry - temporarily disabled
+// var globalTemplUIRegistry *types.UITemplRegistry
-// initTemplUIRegistry initializes the global templ UI registry
+// initTemplUIRegistry initializes the global templ UI registry - temporarily disabled
func initTemplUIRegistry() {
- if globalTemplUIRegistry == nil {
- globalTemplUIRegistry = types.NewUITemplRegistry()
-
- // Register vacuum templ UI provider using shared instances
- vacuumDetector, vacuumScheduler := vacuum.GetSharedInstances()
- vacuum.RegisterUITempl(globalTemplUIRegistry, vacuumDetector, vacuumScheduler)
-
- // Register erasure coding templ UI provider using shared instances
- erasureCodingDetector, erasureCodingScheduler := erasure_coding.GetSharedInstances()
- erasure_coding.RegisterUITempl(globalTemplUIRegistry, erasureCodingDetector, erasureCodingScheduler)
-
- // Register balance templ UI provider using shared instances
- balanceDetector, balanceScheduler := balance.GetSharedInstances()
- balance.RegisterUITempl(globalTemplUIRegistry, balanceDetector, balanceScheduler)
- }
+ // Temporarily disabled due to missing types
+ // if globalTemplUIRegistry == nil {
+ // globalTemplUIRegistry = types.NewUITemplRegistry()
+ // // Register vacuum templ UI provider using shared instances
+ // vacuumDetector, vacuumScheduler := vacuum.GetSharedInstances()
+ // vacuum.RegisterUITempl(globalTemplUIRegistry, vacuumDetector, vacuumScheduler)
+ // // Register erasure coding templ UI provider using shared instances
+ // erasureCodingDetector, erasureCodingScheduler := erasure_coding.GetSharedInstances()
+ // erasure_coding.RegisterUITempl(globalTemplUIRegistry, erasureCodingDetector, erasureCodingScheduler)
+ // // Register balance templ UI provider using shared instances
+ // balanceDetector, balanceScheduler := balance.GetSharedInstances()
+ // balance.RegisterUITempl(globalTemplUIRegistry, balanceDetector, balanceScheduler)
+ // }
}
-// getTemplUIProvider gets the templ UI provider for a task type
-func getTemplUIProvider(taskType maintenance.MaintenanceTaskType) types.TaskUITemplProvider {
- initTemplUIRegistry()
-
+// getTemplUIProvider gets the templ UI provider for a task type - temporarily disabled
+func getTemplUIProvider(taskType maintenance.MaintenanceTaskType) interface{} {
+ // initTemplUIRegistry()
// Convert maintenance task type to worker task type
- typesRegistry := tasks.GetGlobalTypesRegistry()
- for workerTaskType := range typesRegistry.GetAllDetectors() {
- if string(workerTaskType) == string(taskType) {
- return globalTemplUIRegistry.GetProvider(workerTaskType)
- }
- }
-
+ // typesRegistry := tasks.GetGlobalTypesRegistry()
+ // for workerTaskType := range typesRegistry.GetAllDetectors() {
+ // if string(workerTaskType) == string(taskType) {
+ // return globalTemplUIRegistry.GetProvider(workerTaskType)
+ // }
+ // }
return nil
}
diff --git a/weed/admin/handlers/policy_handlers.go b/weed/admin/handlers/policy_handlers.go
new file mode 100644
index 000000000..8f5cc91b1
--- /dev/null
+++ b/weed/admin/handlers/policy_handlers.go
@@ -0,0 +1,273 @@
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/seaweedfs/seaweedfs/weed/admin/dash"
+ "github.com/seaweedfs/seaweedfs/weed/admin/view/app"
+ "github.com/seaweedfs/seaweedfs/weed/admin/view/layout"
+ "github.com/seaweedfs/seaweedfs/weed/credential"
+ "github.com/seaweedfs/seaweedfs/weed/glog"
+)
+
+// PolicyHandlers contains all the HTTP handlers for policy management
+type PolicyHandlers struct {
+ adminServer *dash.AdminServer
+}
+
+// NewPolicyHandlers creates a new instance of PolicyHandlers
+func NewPolicyHandlers(adminServer *dash.AdminServer) *PolicyHandlers {
+ return &PolicyHandlers{
+ adminServer: adminServer,
+ }
+}
+
+// ShowPolicies renders the policies management page
+func (h *PolicyHandlers) ShowPolicies(c *gin.Context) {
+ // Get policies data from the server
+ policiesData := h.getPoliciesData(c)
+
+ // Render HTML template
+ c.Header("Content-Type", "text/html")
+ policiesComponent := app.Policies(policiesData)
+ layoutComponent := layout.Layout(c, policiesComponent)
+ err := layoutComponent.Render(c.Request.Context(), c.Writer)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
+ return
+ }
+}
+
+// GetPolicies returns the list of policies as JSON
+func (h *PolicyHandlers) GetPolicies(c *gin.Context) {
+ policies, err := h.adminServer.GetPolicies()
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get policies: " + err.Error()})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"policies": policies})
+}
+
+// CreatePolicy handles policy creation
+func (h *PolicyHandlers) CreatePolicy(c *gin.Context) {
+ var req dash.CreatePolicyRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
+ return
+ }
+
+ // Validate policy name
+ if req.Name == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Policy name is required"})
+ return
+ }
+
+ // Check if policy already exists
+ existingPolicy, err := h.adminServer.GetPolicy(req.Name)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check existing policy: " + err.Error()})
+ return
+ }
+ if existingPolicy != nil {
+ c.JSON(http.StatusConflict, gin.H{"error": "Policy with this name already exists"})
+ return
+ }
+
+ // Create the policy
+ err = h.adminServer.CreatePolicy(req.Name, req.Document)
+ if err != nil {
+ glog.Errorf("Failed to create policy %s: %v", req.Name, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create policy: " + err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusCreated, gin.H{
+ "success": true,
+ "message": "Policy created successfully",
+ "policy": req.Name,
+ })
+}
+
+// GetPolicy returns a specific policy
+func (h *PolicyHandlers) GetPolicy(c *gin.Context) {
+ policyName := c.Param("name")
+ if policyName == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Policy name is required"})
+ return
+ }
+
+ policy, err := h.adminServer.GetPolicy(policyName)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get policy: " + err.Error()})
+ return
+ }
+
+ if policy == nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Policy not found"})
+ return
+ }
+
+ c.JSON(http.StatusOK, policy)
+}
+
+// UpdatePolicy handles policy updates
+func (h *PolicyHandlers) UpdatePolicy(c *gin.Context) {
+ policyName := c.Param("name")
+ if policyName == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Policy name is required"})
+ return
+ }
+
+ var req dash.UpdatePolicyRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
+ return
+ }
+
+ // Check if policy exists
+ existingPolicy, err := h.adminServer.GetPolicy(policyName)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check existing policy: " + err.Error()})
+ return
+ }
+ if existingPolicy == nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Policy not found"})
+ return
+ }
+
+ // Update the policy
+ err = h.adminServer.UpdatePolicy(policyName, req.Document)
+ if err != nil {
+ glog.Errorf("Failed to update policy %s: %v", policyName, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update policy: " + err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "success": true,
+ "message": "Policy updated successfully",
+ "policy": policyName,
+ })
+}
+
+// DeletePolicy handles policy deletion
+func (h *PolicyHandlers) DeletePolicy(c *gin.Context) {
+ policyName := c.Param("name")
+ if policyName == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Policy name is required"})
+ return
+ }
+
+ // Check if policy exists
+ existingPolicy, err := h.adminServer.GetPolicy(policyName)
+ if err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to check existing policy: " + err.Error()})
+ return
+ }
+ if existingPolicy == nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Policy not found"})
+ return
+ }
+
+ // Delete the policy
+ err = h.adminServer.DeletePolicy(policyName)
+ if err != nil {
+ glog.Errorf("Failed to delete policy %s: %v", policyName, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete policy: " + err.Error()})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "success": true,
+ "message": "Policy deleted successfully",
+ "policy": policyName,
+ })
+}
+
+// ValidatePolicy validates a policy document without saving it
+func (h *PolicyHandlers) ValidatePolicy(c *gin.Context) {
+ var req struct {
+ Document credential.PolicyDocument `json:"document" binding:"required"`
+ }
+
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()})
+ return
+ }
+
+ // Basic validation
+ if req.Document.Version == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Policy version is required"})
+ return
+ }
+
+ if len(req.Document.Statement) == 0 {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Policy must have at least one statement"})
+ return
+ }
+
+ // Validate each statement
+ for i, statement := range req.Document.Statement {
+ if statement.Effect != "Allow" && statement.Effect != "Deny" {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "error": fmt.Sprintf("Statement %d: Effect must be 'Allow' or 'Deny'", i+1),
+ })
+ return
+ }
+
+ if len(statement.Action) == 0 {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "error": fmt.Sprintf("Statement %d: Action is required", i+1),
+ })
+ return
+ }
+
+ if len(statement.Resource) == 0 {
+ c.JSON(http.StatusBadRequest, gin.H{
+ "error": fmt.Sprintf("Statement %d: Resource is required", i+1),
+ })
+ return
+ }
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "valid": true,
+ "message": "Policy document is valid",
+ })
+}
+
+// getPoliciesData retrieves policies data from the server
+func (h *PolicyHandlers) getPoliciesData(c *gin.Context) dash.PoliciesData {
+ username := c.GetString("username")
+ if username == "" {
+ username = "admin"
+ }
+
+ // Get policies
+ policies, err := h.adminServer.GetPolicies()
+ if err != nil {
+ glog.Errorf("Failed to get policies: %v", err)
+ // Return empty data on error
+ return dash.PoliciesData{
+ Username: username,
+ Policies: []dash.IAMPolicy{},
+ TotalPolicies: 0,
+ LastUpdated: time.Now(),
+ }
+ }
+
+ // Ensure policies is never nil
+ if policies == nil {
+ policies = []dash.IAMPolicy{}
+ }
+
+ return dash.PoliciesData{
+ Username: username,
+ Policies: policies,
+ TotalPolicies: len(policies),
+ LastUpdated: time.Now(),
+ }
+}