You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							297 lines
						
					
					
						
							8.8 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							297 lines
						
					
					
						
							8.8 KiB
						
					
					
				| package dash | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"net/http" | |
| 	"time" | |
| 
 | |
| 	"github.com/gin-gonic/gin" | |
| 	"github.com/seaweedfs/seaweedfs/weed/cluster" | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| 	"github.com/seaweedfs/seaweedfs/weed/pb/master_pb" | |
| ) | |
| 
 | |
| type AdminData struct { | |
| 	Username          string              `json:"username"` | |
| 	TotalVolumes      int                 `json:"total_volumes"` | |
| 	TotalFiles        int64               `json:"total_files"` | |
| 	TotalSize         int64               `json:"total_size"` | |
| 	VolumeSizeLimitMB uint64              `json:"volume_size_limit_mb"` | |
| 	MasterNodes       []MasterNode        `json:"master_nodes"` | |
| 	VolumeServers     []VolumeServer      `json:"volume_servers"` | |
| 	FilerNodes        []FilerNode         `json:"filer_nodes"` | |
| 	MessageBrokers    []MessageBrokerNode `json:"message_brokers"` | |
| 	DataCenters       []DataCenter        `json:"datacenters"` | |
| 	LastUpdated       time.Time           `json:"last_updated"` | |
| 
 | |
| 	// EC shard totals for dashboard | |
| 	TotalEcVolumes int `json:"total_ec_volumes"` // Total number of EC volumes across all servers | |
| 	TotalEcShards  int `json:"total_ec_shards"`  // Total number of EC shards across all servers | |
| } | |
| 
 | |
| // Object Store Users management structures | |
| type ObjectStoreUser struct { | |
| 	Username    string   `json:"username"` | |
| 	Email       string   `json:"email"` | |
| 	AccessKey   string   `json:"access_key"` | |
| 	SecretKey   string   `json:"secret_key"` | |
| 	Permissions []string `json:"permissions"` | |
| } | |
| 
 | |
| type ObjectStoreUsersData struct { | |
| 	Username    string            `json:"username"` | |
| 	Users       []ObjectStoreUser `json:"users"` | |
| 	TotalUsers  int               `json:"total_users"` | |
| 	LastUpdated time.Time         `json:"last_updated"` | |
| } | |
| 
 | |
| // User management request structures | |
| type CreateUserRequest struct { | |
| 	Username    string   `json:"username" binding:"required"` | |
| 	Email       string   `json:"email"` | |
| 	Actions     []string `json:"actions"` | |
| 	GenerateKey bool     `json:"generate_key"` | |
| } | |
| 
 | |
| type UpdateUserRequest struct { | |
| 	Email   string   `json:"email"` | |
| 	Actions []string `json:"actions"` | |
| } | |
| 
 | |
| type UpdateUserPoliciesRequest struct { | |
| 	Actions []string `json:"actions" binding:"required"` | |
| } | |
| 
 | |
| type AccessKeyInfo struct { | |
| 	AccessKey string    `json:"access_key"` | |
| 	SecretKey string    `json:"secret_key"` | |
| 	CreatedAt time.Time `json:"created_at"` | |
| } | |
| 
 | |
| type UserDetails struct { | |
| 	Username   string          `json:"username"` | |
| 	Email      string          `json:"email"` | |
| 	Actions    []string        `json:"actions"` | |
| 	AccessKeys []AccessKeyInfo `json:"access_keys"` | |
| } | |
| 
 | |
| type FilerNode struct { | |
| 	Address     string    `json:"address"` | |
| 	DataCenter  string    `json:"datacenter"` | |
| 	Rack        string    `json:"rack"` | |
| 	LastUpdated time.Time `json:"last_updated"` | |
| } | |
| 
 | |
| type MessageBrokerNode struct { | |
| 	Address     string    `json:"address"` | |
| 	DataCenter  string    `json:"datacenter"` | |
| 	Rack        string    `json:"rack"` | |
| 	LastUpdated time.Time `json:"last_updated"` | |
| } | |
| 
 | |
| // GetAdminData retrieves admin data as a struct (for reuse by both JSON and HTML handlers) | |
| func (s *AdminServer) GetAdminData(username string) (AdminData, error) { | |
| 	if username == "" { | |
| 		username = "admin" | |
| 	} | |
| 
 | |
| 	// Get cluster topology | |
| 	topology, err := s.GetClusterTopology() | |
| 	if err != nil { | |
| 		glog.Errorf("Failed to get cluster topology: %v", err) | |
| 		return AdminData{}, err | |
| 	} | |
| 
 | |
| 	// Get volume servers data with EC shard information | |
| 	volumeServersData, err := s.GetClusterVolumeServers() | |
| 	if err != nil { | |
| 		glog.Errorf("Failed to get cluster volume servers: %v", err) | |
| 		return AdminData{}, err | |
| 	} | |
| 
 | |
| 	// Get master nodes status | |
| 	masterNodes := s.getMasterNodesStatus() | |
| 
 | |
| 	// Get filer nodes status | |
| 	filerNodes := s.getFilerNodesStatus() | |
| 
 | |
| 	// Get message broker nodes status | |
| 	messageBrokers := s.getMessageBrokerNodesStatus() | |
| 
 | |
| 	// Get volume size limit from master configuration | |
| 	var volumeSizeLimitMB uint64 = 30000 // Default to 30GB | |
| 	err = s.WithMasterClient(func(client master_pb.SeaweedClient) error { | |
| 		resp, err := client.GetMasterConfiguration(context.Background(), &master_pb.GetMasterConfigurationRequest{}) | |
| 		if err != nil { | |
| 			return err | |
| 		} | |
| 		volumeSizeLimitMB = uint64(resp.VolumeSizeLimitMB) | |
| 		return nil | |
| 	}) | |
| 	if err != nil { | |
| 		glog.Warningf("Failed to get volume size limit from master: %v", err) | |
| 		// Keep default value on error | |
| 	} | |
| 
 | |
| 	// Calculate EC shard totals | |
| 	var totalEcVolumes, totalEcShards int | |
| 	ecVolumeSet := make(map[uint32]bool) // To avoid counting the same EC volume multiple times | |
|  | |
| 	for _, vs := range volumeServersData.VolumeServers { | |
| 		totalEcShards += vs.EcShards | |
| 		// Count unique EC volumes across all servers | |
| 		for _, ecInfo := range vs.EcShardDetails { | |
| 			ecVolumeSet[ecInfo.VolumeID] = true | |
| 		} | |
| 	} | |
| 	totalEcVolumes = len(ecVolumeSet) | |
| 
 | |
| 	// Prepare admin data | |
| 	adminData := AdminData{ | |
| 		Username:          username, | |
| 		TotalVolumes:      topology.TotalVolumes, | |
| 		TotalFiles:        topology.TotalFiles, | |
| 		TotalSize:         topology.TotalSize, | |
| 		VolumeSizeLimitMB: volumeSizeLimitMB, | |
| 		MasterNodes:       masterNodes, | |
| 		VolumeServers:     volumeServersData.VolumeServers, | |
| 		FilerNodes:        filerNodes, | |
| 		MessageBrokers:    messageBrokers, | |
| 		DataCenters:       topology.DataCenters, | |
| 		LastUpdated:       topology.UpdatedAt, | |
| 		TotalEcVolumes:    totalEcVolumes, | |
| 		TotalEcShards:     totalEcShards, | |
| 	} | |
| 
 | |
| 	return adminData, nil | |
| } | |
| 
 | |
| // ShowAdmin displays the main admin page (now uses GetAdminData) | |
| func (s *AdminServer) ShowAdmin(c *gin.Context) { | |
| 	username := c.GetString("username") | |
| 
 | |
| 	adminData, err := s.GetAdminData(username) | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get admin data: " + err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	// Return JSON for API calls | |
| 	c.JSON(http.StatusOK, adminData) | |
| } | |
| 
 | |
| // ShowOverview displays cluster overview | |
| func (s *AdminServer) ShowOverview(c *gin.Context) { | |
| 	topology, err := s.GetClusterTopology() | |
| 	if err != nil { | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | |
| 		return | |
| 	} | |
| 
 | |
| 	c.JSON(http.StatusOK, topology) | |
| } | |
| 
 | |
| // getMasterNodesStatus checks status of all master nodes | |
| func (s *AdminServer) getMasterNodesStatus() []MasterNode { | |
| 	var masterNodes []MasterNode | |
| 
 | |
| 	// Since we have a single master address, create one entry | |
| 	var isLeader bool = true // Assume leader since it's the only master we know about | |
|  | |
| 	// Try to get leader info from this master | |
| 	err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { | |
| 		_, err := client.GetMasterConfiguration(context.Background(), &master_pb.GetMasterConfigurationRequest{}) | |
| 		if err != nil { | |
| 			return err | |
| 		} | |
| 		// For now, assume this master is the leader since we can connect to it | |
| 		isLeader = true | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		isLeader = false | |
| 	} | |
| 
 | |
| 	currentMaster := s.masterClient.GetMaster(context.Background()) | |
| 	if currentMaster != "" { | |
| 		masterNodes = append(masterNodes, MasterNode{ | |
| 			Address:  string(currentMaster), | |
| 			IsLeader: isLeader, | |
| 		}) | |
| 	} | |
| 
 | |
| 	return masterNodes | |
| } | |
| 
 | |
| // getFilerNodesStatus checks status of all filer nodes using master's ListClusterNodes | |
| func (s *AdminServer) getFilerNodesStatus() []FilerNode { | |
| 	var filerNodes []FilerNode | |
| 
 | |
| 	// Get filer nodes from master using ListClusterNodes | |
| 	err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { | |
| 		resp, err := client.ListClusterNodes(context.Background(), &master_pb.ListClusterNodesRequest{ | |
| 			ClientType: cluster.FilerType, | |
| 		}) | |
| 		if err != nil { | |
| 			return err | |
| 		} | |
| 
 | |
| 		// Process each filer node | |
| 		for _, node := range resp.ClusterNodes { | |
| 			filerNodes = append(filerNodes, FilerNode{ | |
| 				Address:     node.Address, | |
| 				DataCenter:  node.DataCenter, | |
| 				Rack:        node.Rack, | |
| 				LastUpdated: time.Now(), | |
| 			}) | |
| 		} | |
| 
 | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		currentMaster := s.masterClient.GetMaster(context.Background()) | |
| 		glog.Errorf("Failed to get filer nodes from master %s: %v", currentMaster, err) | |
| 		// Return empty list if we can't get filer info from master | |
| 		return []FilerNode{} | |
| 	} | |
| 
 | |
| 	return filerNodes | |
| } | |
| 
 | |
| // getMessageBrokerNodesStatus checks status of all message broker nodes using master's ListClusterNodes | |
| func (s *AdminServer) getMessageBrokerNodesStatus() []MessageBrokerNode { | |
| 	var messageBrokers []MessageBrokerNode | |
| 
 | |
| 	// Get message broker nodes from master using ListClusterNodes | |
| 	err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { | |
| 		resp, err := client.ListClusterNodes(context.Background(), &master_pb.ListClusterNodesRequest{ | |
| 			ClientType: cluster.BrokerType, | |
| 		}) | |
| 		if err != nil { | |
| 			return err | |
| 		} | |
| 
 | |
| 		// Process each message broker node | |
| 		for _, node := range resp.ClusterNodes { | |
| 			messageBrokers = append(messageBrokers, MessageBrokerNode{ | |
| 				Address:     node.Address, | |
| 				DataCenter:  node.DataCenter, | |
| 				Rack:        node.Rack, | |
| 				LastUpdated: time.Now(), | |
| 			}) | |
| 		} | |
| 
 | |
| 		return nil | |
| 	}) | |
| 
 | |
| 	if err != nil { | |
| 		currentMaster := s.masterClient.GetMaster(context.Background()) | |
| 		glog.Errorf("Failed to get message broker nodes from master %s: %v", currentMaster, err) | |
| 		// Return empty list if we can't get broker info from master | |
| 		return []MessageBrokerNode{} | |
| 	} | |
| 
 | |
| 	return messageBrokers | |
| }
 |