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
This commit is contained in:
@@ -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")
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,53 +215,54 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// } // 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
|
||||
}
|
||||
|
||||
273
weed/admin/handlers/policy_handlers.go
Normal file
273
weed/admin/handlers/policy_handlers.go
Normal file
@@ -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(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user