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.
		
		
		
		
		
			
		
			
				
					
					
						
							313 lines
						
					
					
						
							15 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							313 lines
						
					
					
						
							15 KiB
						
					
					
				
								package app
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
								    "fmt"
							 | 
						|
								    "github.com/seaweedfs/seaweedfs/weed/admin/dash"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								templ EcVolumeDetails(data dash.EcVolumeDetailsData) {
							 | 
						|
								    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
							 | 
						|
								        <div>
							 | 
						|
								            <h1 class="h2">
							 | 
						|
								                <i class="fas fa-th-large me-2"></i>EC Volume Details
							 | 
						|
								            </h1>
							 | 
						|
								            <nav aria-label="breadcrumb">
							 | 
						|
								                <ol class="breadcrumb">
							 | 
						|
								                    <li class="breadcrumb-item"><a href="/admin" class="text-decoration-none">Dashboard</a></li>
							 | 
						|
								                    <li class="breadcrumb-item"><a href="/cluster/ec-shards" class="text-decoration-none">EC Volumes</a></li>
							 | 
						|
								                    <li class="breadcrumb-item active" aria-current="page">Volume {fmt.Sprintf("%d", data.VolumeID)}</li>
							 | 
						|
								                </ol>
							 | 
						|
								            </nav>
							 | 
						|
								        </div>
							 | 
						|
								        <div class="btn-toolbar mb-2 mb-md-0">
							 | 
						|
								            <div class="btn-group me-2">
							 | 
						|
								                <button type="button" class="btn btn-sm btn-outline-secondary" onclick="history.back()">
							 | 
						|
								                    <i class="fas fa-arrow-left me-1"></i>Back
							 | 
						|
								                </button>
							 | 
						|
								                <button type="button" class="btn btn-sm btn-outline-primary" onclick="window.location.reload()">
							 | 
						|
								                    <i class="fas fa-refresh me-1"></i>Refresh
							 | 
						|
								                </button>
							 | 
						|
								            </div>
							 | 
						|
								        </div>
							 | 
						|
								    </div>
							 | 
						|
								
							 | 
						|
								    <!-- EC Volume Summary -->
							 | 
						|
								    <div class="row mb-4">
							 | 
						|
								        <div class="col-md-6">
							 | 
						|
								            <div class="card">
							 | 
						|
								                <div class="card-header">
							 | 
						|
								                    <h5 class="card-title mb-0">
							 | 
						|
								                        <i class="fas fa-info-circle me-2"></i>Volume Information
							 | 
						|
								                    </h5>
							 | 
						|
								                </div>
							 | 
						|
								                <div class="card-body">
							 | 
						|
								                    <table class="table table-borderless">
							 | 
						|
								                        <tr>
							 | 
						|
								                            <td><strong>Volume ID:</strong></td>
							 | 
						|
								                            <td>{fmt.Sprintf("%d", data.VolumeID)}</td>
							 | 
						|
								                        </tr>
							 | 
						|
								                        <tr>
							 | 
						|
								                            <td><strong>Collection:</strong></td>
							 | 
						|
								                            <td>
							 | 
						|
								                                if data.Collection != "" {
							 | 
						|
								                                    <span class="badge bg-info">{data.Collection}</span>
							 | 
						|
								                                } else {
							 | 
						|
								                                    <span class="text-muted">default</span>
							 | 
						|
								                                }
							 | 
						|
								                            </td>
							 | 
						|
								                        </tr>
							 | 
						|
								                        <tr>
							 | 
						|
								                            <td><strong>Status:</strong></td>
							 | 
						|
								                            <td>
							 | 
						|
								                                if data.IsComplete {
							 | 
						|
								                                    <span class="badge bg-success">
							 | 
						|
								                                        <i class="fas fa-check me-1"></i>Complete ({data.TotalShards}/14 shards)
							 | 
						|
								                                    </span>
							 | 
						|
								                                } else {
							 | 
						|
								                                    <span class="badge bg-warning">
							 | 
						|
								                                        <i class="fas fa-exclamation-triangle me-1"></i>Incomplete ({data.TotalShards}/14 shards)
							 | 
						|
								                                    </span>
							 | 
						|
								                                }
							 | 
						|
								                            </td>
							 | 
						|
								                        </tr>
							 | 
						|
								                        if !data.IsComplete {
							 | 
						|
								                            <tr>
							 | 
						|
								                                <td><strong>Missing Shards:</strong></td>
							 | 
						|
								                                <td>
							 | 
						|
								                                    for i, shardID := range data.MissingShards {
							 | 
						|
								                                        if i > 0 { 
							 | 
						|
								                                            <span>, </span>
							 | 
						|
								                                        }
							 | 
						|
								                                        <span class="badge bg-danger">{fmt.Sprintf("%02d", shardID)}</span>
							 | 
						|
								                                    }
							 | 
						|
								                                </td>
							 | 
						|
								                            </tr>
							 | 
						|
								                        }
							 | 
						|
								                        <tr>
							 | 
						|
								                            <td><strong>Data Centers:</strong></td>
							 | 
						|
								                            <td>
							 | 
						|
								                                for i, dc := range data.DataCenters {
							 | 
						|
								                                    if i > 0 { 
							 | 
						|
								                                        <span>, </span>
							 | 
						|
								                                    }
							 | 
						|
								                                    <span class="badge bg-primary">{dc}</span>
							 | 
						|
								                                }
							 | 
						|
								                            </td>
							 | 
						|
								                        </tr>
							 | 
						|
								                        <tr>
							 | 
						|
								                            <td><strong>Servers:</strong></td>
							 | 
						|
								                            <td>
							 | 
						|
								                                <span class="text-muted">{fmt.Sprintf("%d servers", len(data.Servers))}</span>
							 | 
						|
								                            </td>
							 | 
						|
								                        </tr>
							 | 
						|
								                        <tr>
							 | 
						|
								                            <td><strong>Last Updated:</strong></td>
							 | 
						|
								                            <td>
							 | 
						|
								                                <span class="text-muted">{data.LastUpdated.Format("2006-01-02 15:04:05")}</span>
							 | 
						|
								                            </td>
							 | 
						|
								                        </tr>
							 | 
						|
								                    </table>
							 | 
						|
								                </div>
							 | 
						|
								            </div>
							 | 
						|
								        </div>
							 | 
						|
								        
							 | 
						|
								        <div class="col-md-6">
							 | 
						|
								            <div class="card">
							 | 
						|
								                <div class="card-header">
							 | 
						|
								                    <h5 class="card-title mb-0">
							 | 
						|
								                        <i class="fas fa-chart-pie me-2"></i>Shard Distribution
							 | 
						|
								                    </h5>
							 | 
						|
								                </div>
							 | 
						|
								                <div class="card-body">
							 | 
						|
								                    <div class="row text-center">
							 | 
						|
								                        <div class="col-4">
							 | 
						|
								                            <div class="border rounded p-3">
							 | 
						|
								                                <h3 class="text-primary mb-1">{fmt.Sprintf("%d", data.TotalShards)}</h3>
							 | 
						|
								                                <small class="text-muted">Total Shards</small>
							 | 
						|
								                            </div>
							 | 
						|
								                        </div>
							 | 
						|
								                        <div class="col-4">
							 | 
						|
								                            <div class="border rounded p-3">
							 | 
						|
								                                <h3 class="text-success mb-1">{fmt.Sprintf("%d", len(data.DataCenters))}</h3>
							 | 
						|
								                                <small class="text-muted">Data Centers</small>
							 | 
						|
								                            </div>
							 | 
						|
								                        </div>
							 | 
						|
								                        <div class="col-4">
							 | 
						|
								                            <div class="border rounded p-3">
							 | 
						|
								                                <h3 class="text-info mb-1">{fmt.Sprintf("%d", len(data.Servers))}</h3>
							 | 
						|
								                                <small class="text-muted">Servers</small>
							 | 
						|
								                            </div>
							 | 
						|
								                        </div>
							 | 
						|
								                    </div>
							 | 
						|
								                    
							 | 
						|
								                    <!-- Shard Distribution Visualization -->
							 | 
						|
								                    <div class="mt-3">
							 | 
						|
								                        <h6>Present Shards:</h6>
							 | 
						|
								                        <div class="d-flex flex-wrap gap-1">
							 | 
						|
								                            for _, shard := range data.Shards {
							 | 
						|
								                                <span class="badge bg-success me-1 mb-1">{fmt.Sprintf("%02d", shard.ShardID)}</span>
							 | 
						|
								                            }
							 | 
						|
								                        </div>
							 | 
						|
								                        if len(data.MissingShards) > 0 {
							 | 
						|
								                            <h6 class="mt-2">Missing Shards:</h6>
							 | 
						|
								                            <div class="d-flex flex-wrap gap-1">
							 | 
						|
								                                for _, shardID := range data.MissingShards {
							 | 
						|
								                                    <span class="badge bg-secondary me-1 mb-1">{fmt.Sprintf("%02d", shardID)}</span>
							 | 
						|
								                                }
							 | 
						|
								                            </div>
							 | 
						|
								                        }
							 | 
						|
								                    </div>
							 | 
						|
								                </div>
							 | 
						|
								            </div>
							 | 
						|
								        </div>
							 | 
						|
								    </div>
							 | 
						|
								
							 | 
						|
								    <!-- Shard Details Table -->
							 | 
						|
								    <div class="card">
							 | 
						|
								        <div class="card-header">
							 | 
						|
								            <h5 class="card-title mb-0">
							 | 
						|
								                <i class="fas fa-list me-2"></i>Shard Details
							 | 
						|
								            </h5>
							 | 
						|
								        </div>
							 | 
						|
								        <div class="card-body">
							 | 
						|
								            if len(data.Shards) > 0 {
							 | 
						|
								                <div class="table-responsive">
							 | 
						|
								                    <table class="table table-striped table-hover">
							 | 
						|
								                        <thead>
							 | 
						|
								                            <tr>
							 | 
						|
								                                <th>
							 | 
						|
								                                    <a href="#" onclick="sortBy('shard_id')" class="text-dark text-decoration-none">
							 | 
						|
								                                        Shard ID
							 | 
						|
								                                        if data.SortBy == "shard_id" {
							 | 
						|
								                                            if data.SortOrder == "asc" {
							 | 
						|
								                                                <i class="fas fa-sort-up ms-1"></i>
							 | 
						|
								                                            } else {
							 | 
						|
								                                                <i class="fas fa-sort-down ms-1"></i>
							 | 
						|
								                                            }
							 | 
						|
								                                        } else {
							 | 
						|
								                                            <i class="fas fa-sort ms-1 text-muted"></i>
							 | 
						|
								                                        }
							 | 
						|
								                                    </a>
							 | 
						|
								                                </th>
							 | 
						|
								                                <th>
							 | 
						|
								                                    <a href="#" onclick="sortBy('server')" class="text-dark text-decoration-none">
							 | 
						|
								                                        Server
							 | 
						|
								                                        if data.SortBy == "server" {
							 | 
						|
								                                            if data.SortOrder == "asc" {
							 | 
						|
								                                                <i class="fas fa-sort-up ms-1"></i>
							 | 
						|
								                                            } else {
							 | 
						|
								                                                <i class="fas fa-sort-down ms-1"></i>
							 | 
						|
								                                            }
							 | 
						|
								                                        } else {
							 | 
						|
								                                            <i class="fas fa-sort ms-1 text-muted"></i>
							 | 
						|
								                                        }
							 | 
						|
								                                    </a>
							 | 
						|
								                                </th>
							 | 
						|
								                                <th>
							 | 
						|
								                                    <a href="#" onclick="sortBy('data_center')" class="text-dark text-decoration-none">
							 | 
						|
								                                        Data Center
							 | 
						|
								                                        if data.SortBy == "data_center" {
							 | 
						|
								                                            if data.SortOrder == "asc" {
							 | 
						|
								                                                <i class="fas fa-sort-up ms-1"></i>
							 | 
						|
								                                            } else {
							 | 
						|
								                                                <i class="fas fa-sort-down ms-1"></i>
							 | 
						|
								                                            }
							 | 
						|
								                                        } else {
							 | 
						|
								                                            <i class="fas fa-sort ms-1 text-muted"></i>
							 | 
						|
								                                        }
							 | 
						|
								                                    </a>
							 | 
						|
								                                </th>
							 | 
						|
								                                <th>
							 | 
						|
								                                    <a href="#" onclick="sortBy('rack')" class="text-dark text-decoration-none">
							 | 
						|
								                                        Rack
							 | 
						|
								                                        if data.SortBy == "rack" {
							 | 
						|
								                                            if data.SortOrder == "asc" {
							 | 
						|
								                                                <i class="fas fa-sort-up ms-1"></i>
							 | 
						|
								                                            } else {
							 | 
						|
								                                                <i class="fas fa-sort-down ms-1"></i>
							 | 
						|
								                                            }
							 | 
						|
								                                        } else {
							 | 
						|
								                                            <i class="fas fa-sort ms-1 text-muted"></i>
							 | 
						|
								                                        }
							 | 
						|
								                                    </a>
							 | 
						|
								                                </th>
							 | 
						|
								                                <th class="text-dark">Disk Type</th>
							 | 
						|
								                                <th class="text-dark">Shard Size</th>
							 | 
						|
								                                <th class="text-dark">Actions</th>
							 | 
						|
								                            </tr>
							 | 
						|
								                        </thead>
							 | 
						|
								                        <tbody>
							 | 
						|
								                            for _, shard := range data.Shards {
							 | 
						|
								                                    <tr>
							 | 
						|
								                                        <td>
							 | 
						|
								                                            <span class="badge bg-primary">{fmt.Sprintf("%02d", shard.ShardID)}</span>
							 | 
						|
								                                        </td>
							 | 
						|
								                                        <td>
							 | 
						|
								                                            <a href={ templ.URL("/cluster/volume-servers/" + shard.Server) } class="text-primary text-decoration-none">
							 | 
						|
								                                                <code class="small">{shard.Server}</code>
							 | 
						|
								                                            </a>
							 | 
						|
								                                        </td>
							 | 
						|
								                                        <td>
							 | 
						|
								                                            <span class="badge bg-primary text-white">{shard.DataCenter}</span>
							 | 
						|
								                                        </td>
							 | 
						|
								                                        <td>
							 | 
						|
								                                            <span class="badge bg-secondary text-white">{shard.Rack}</span>
							 | 
						|
								                                        </td>
							 | 
						|
								                                        <td>
							 | 
						|
								                                            <span class="text-dark">{shard.DiskType}</span>
							 | 
						|
								                                        </td>
							 | 
						|
								                                        <td>
							 | 
						|
								                                            <span class="text-success">{bytesToHumanReadableUint64(shard.Size)}</span>
							 | 
						|
								                                        </td>
							 | 
						|
								                                        <td>
							 | 
						|
								                                            <a href={ templ.SafeURL(fmt.Sprintf("http://%s/ui/index.html", shard.Server)) } target="_blank" class="btn btn-sm btn-primary">
							 | 
						|
								                                                <i class="fas fa-external-link-alt me-1"></i>Volume Server
							 | 
						|
								                                            </a>
							 | 
						|
								                                        </td>
							 | 
						|
								                                    </tr>
							 | 
						|
								                                }
							 | 
						|
								                        </tbody>
							 | 
						|
								                    </table>
							 | 
						|
								                </div>
							 | 
						|
								            } else {
							 | 
						|
								                <div class="text-center py-4">
							 | 
						|
								                    <i class="fas fa-exclamation-triangle fa-3x text-warning mb-3"></i>
							 | 
						|
								                    <h5>No EC shards found</h5>
							 | 
						|
								                    <p class="text-muted">This volume may not be EC encoded yet.</p>
							 | 
						|
								                </div>
							 | 
						|
								            }
							 | 
						|
								        </div>
							 | 
						|
								    </div>
							 | 
						|
								    
							 | 
						|
								    <script>
							 | 
						|
								        // Sorting functionality
							 | 
						|
								        function sortBy(field) {
							 | 
						|
								            const currentSort = new URLSearchParams(window.location.search).get('sort_by');
							 | 
						|
								            const currentOrder = new URLSearchParams(window.location.search).get('sort_order') || 'asc';
							 | 
						|
								            
							 | 
						|
								            let newOrder = 'asc';
							 | 
						|
								            if (currentSort === field && currentOrder === 'asc') {
							 | 
						|
								                newOrder = 'desc';
							 | 
						|
								            }
							 | 
						|
								            
							 | 
						|
								            const url = new URL(window.location);
							 | 
						|
								            url.searchParams.set('sort_by', field);
							 | 
						|
								            url.searchParams.set('sort_order', newOrder);
							 | 
						|
								            window.location.href = url.toString();
							 | 
						|
								        }
							 | 
						|
								    </script>
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Helper function to convert bytes to human readable format (uint64 version)
							 | 
						|
								func bytesToHumanReadableUint64(bytes uint64) string {
							 | 
						|
								    const unit = 1024
							 | 
						|
								    if bytes < unit {
							 | 
						|
								        return fmt.Sprintf("%dB", bytes)
							 | 
						|
								    }
							 | 
						|
								    div, exp := uint64(unit), 0
							 | 
						|
								    for n := bytes / unit; n >= unit; n /= unit {
							 | 
						|
								        div *= unit
							 | 
						|
								        exp++
							 | 
						|
								    }
							 | 
						|
								    return fmt.Sprintf("%.1f%cB", float64(bytes)/float64(div), "KMGTPE"[exp])
							 | 
						|
								} 
							 |