aboutsummaryrefslogtreecommitdiff
path: root/weed/admin/view/app
diff options
context:
space:
mode:
authorchrislu <chris.lu@gmail.com>2025-07-04 12:49:34 -0700
committerchrislu <chris.lu@gmail.com>2025-07-04 12:49:34 -0700
commit84d4ea0995a9cc01808f3fedbf1b671a6538be32 (patch)
tree3971a806257c01b8f5b8173242ed6126af635ab2 /weed/admin/view/app
parent2cfe079a1fac21d5d654d0e6ed2f1996d5b89209 (diff)
downloadseaweedfs-84d4ea0995a9cc01808f3fedbf1b671a6538be32.tar.xz
seaweedfs-84d4ea0995a9cc01808f3fedbf1b671a6538be32.zip
show volume details
Diffstat (limited to 'weed/admin/view/app')
-rw-r--r--weed/admin/view/app/cluster_volumes.templ43
-rw-r--r--weed/admin/view/app/cluster_volumes_templ.go165
-rw-r--r--weed/admin/view/app/volume_details.templ425
-rw-r--r--weed/admin/view/app/volume_details_templ.go690
4 files changed, 1235 insertions, 88 deletions
diff --git a/weed/admin/view/app/cluster_volumes.templ b/weed/admin/view/app/cluster_volumes.templ
index 1973fdcdb..b21918066 100644
--- a/weed/admin/view/app/cluster_volumes.templ
+++ b/weed/admin/view/app/cluster_volumes.templ
@@ -312,7 +312,7 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
for _, volume := range data.Volumes {
<tr>
<td>
- <code>{fmt.Sprintf("%d", volume.ID)}</code>
+ <code>{fmt.Sprintf("%d", volume.Id)}</code>
</td>
<td>
<a href={templ.SafeURL(fmt.Sprintf("http://%s", volume.Server))} target="_blank" class="text-decoration-none">
@@ -343,11 +343,11 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
}
</td>
}
- <td>{formatBytes(volume.Size)}</td>
- <td>{fmt.Sprintf("%d", volume.FileCount)}</td>
- <td>
- <span class="badge bg-info">{volume.Replication}</span>
- </td>
+ <td>{formatBytes(int64(volume.Size))}</td>
+ <td>{fmt.Sprintf("%d", volume.FileCount)}</td>
+ <td>
+ <span class="badge bg-info">{fmt.Sprintf("%03d", volume.ReplicaPlacement)}</span>
+ </td>
if data.ShowDiskTypeColumn {
<td>
<span class="badge bg-primary">{volume.DiskType}</span>
@@ -360,8 +360,8 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
}
<td>
<div class="btn-group btn-group-sm">
- <button type="button" class="btn btn-outline-primary btn-sm"
- title="View Details">
+ <button type="button" class="btn btn-outline-primary btn-sm view-details-btn"
+ title="View Details" data-volume-id={fmt.Sprintf("%d", volume.Id)}>
<i class="fas fa-eye"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm"
@@ -480,6 +480,15 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
goToPage(page);
});
});
+
+ // Add click handlers to view details buttons
+ document.querySelectorAll('.view-details-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const volumeId = this.getAttribute('data-volume-id');
+ viewVolumeDetails(volumeId);
+ });
+ });
});
function goToPage(page) {
@@ -516,15 +525,25 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
// TODO: Implement volume export functionality
alert('Export functionality to be implemented');
}
+
+ function viewVolumeDetails(volumeId) {
+ // Get the server from the current row
+ const button = event.target.closest('button');
+ const row = button.closest('tr');
+ const serverCell = row.querySelector('td:nth-child(2) a');
+ const server = serverCell ? serverCell.textContent.trim() : 'unknown';
+
+ window.location.href = `/cluster/volumes/${volumeId}/${encodeURIComponent(server)}`;
+ }
</script>
}
-func countActiveVolumes(volumes []dash.VolumeInfo) int {
+func countActiveVolumes(volumes []dash.VolumeWithTopology) int {
// Since we removed status tracking, consider all volumes as active
return len(volumes)
}
-func countUniqueDataCenters(volumes []dash.VolumeInfo) int {
+func countUniqueDataCenters(volumes []dash.VolumeWithTopology) int {
dcMap := make(map[string]bool)
for _, volume := range volumes {
dcMap[volume.DataCenter] = true
@@ -532,7 +551,7 @@ func countUniqueDataCenters(volumes []dash.VolumeInfo) int {
return len(dcMap)
}
-func countUniqueRacks(volumes []dash.VolumeInfo) int {
+func countUniqueRacks(volumes []dash.VolumeWithTopology) int {
rackMap := make(map[string]bool)
for _, volume := range volumes {
if volume.Rack != "" {
@@ -542,7 +561,7 @@ func countUniqueRacks(volumes []dash.VolumeInfo) int {
return len(rackMap)
}
-func countUniqueDiskTypes(volumes []dash.VolumeInfo) int {
+func countUniqueDiskTypes(volumes []dash.VolumeWithTopology) int {
diskTypeMap := make(map[string]bool)
for _, volume := range volumes {
diskType := volume.DiskType
diff --git a/weed/admin/view/app/cluster_volumes_templ.go b/weed/admin/view/app/cluster_volumes_templ.go
index 8d9f1cbe4..3133ab8bd 100644
--- a/weed/admin/view/app/cluster_volumes_templ.go
+++ b/weed/admin/view/app/cluster_volumes_templ.go
@@ -457,7 +457,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.ID))
+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.Id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 315, Col: 79}
}
@@ -586,9 +586,9 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(volume.Size))
+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(volume.Size)))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 346, Col: 69}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 346, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
@@ -601,7 +601,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.FileCount))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 347, Col: 80}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 347, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
@@ -612,9 +612,9 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
- templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(volume.Replication)
+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%03d", volume.ReplicaPlacement))
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 349, Col: 91}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 349, Col: 101}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
@@ -662,228 +662,241 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<td><div class=\"btn-group btn-group-sm\"><button type=\"button\" class=\"btn btn-outline-primary btn-sm\" title=\"View Details\"><i class=\"fas fa-eye\"></i></button> <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" title=\"Compact\"><i class=\"fas fa-compress-alt\"></i></button> <button type=\"button\" class=\"btn btn-outline-warning btn-sm\" title=\"Fix\"><i class=\"fas fa-wrench\"></i></button></div></td></tr>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<td><div class=\"btn-group btn-group-sm\"><button type=\"button\" class=\"btn btn-outline-primary btn-sm view-details-btn\" title=\"View Details\" data-volume-id=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var28 string
+ templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.Id))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 364, Col: 121}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\"><i class=\"fas fa-eye\"></i></button> <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" title=\"Compact\"><i class=\"fas fa-compress-alt\"></i></button> <button type=\"button\" class=\"btn btn-outline-warning btn-sm\" title=\"Fix\"><i class=\"fas fa-wrench\"></i></button></div></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</tbody></table></div><!-- Volume Summary --> <div class=\"d-flex justify-content-between align-items-center mt-3\"><div><small class=\"text-muted\">Showing ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</tbody></table></div><!-- Volume Summary --> <div class=\"d-flex justify-content-between align-items-center mt-3\"><div><small class=\"text-muted\">Showing ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var28 string
- templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", (data.CurrentPage-1)*data.PageSize+1))
+ var templ_7745c5c3_Var29 string
+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", (data.CurrentPage-1)*data.PageSize+1))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 387, Col: 98}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, " to ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, " to ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var29 string
- templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", minInt(data.CurrentPage*data.PageSize, data.TotalVolumes)))
+ var templ_7745c5c3_Var30 string
+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", minInt(data.CurrentPage*data.PageSize, data.TotalVolumes)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 387, Col: 180}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, " of ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, " of ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var30 string
- templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalVolumes))
+ var templ_7745c5c3_Var31 string
+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalVolumes))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 387, Col: 222}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, " volumes</small></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " volumes</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.TotalPages > 1 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<div><small class=\"text-muted\">Page ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<div><small class=\"text-muted\">Page ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var31 string
- templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage))
+ var templ_7745c5c3_Var32 string
+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 393, Col: 77}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, " of ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, " of ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var32 string
- templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalPages))
+ var templ_7745c5c3_Var33 string
+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalPages))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 393, Col: 117}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "</small></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</div><!-- Pagination Controls --> ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "</div><!-- Pagination Controls --> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.TotalPages > 1 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<div class=\"d-flex justify-content-center mt-3\"><nav aria-label=\"Volumes pagination\"><ul class=\"pagination pagination-sm mb-0\"><!-- Previous Button -->")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<div class=\"d-flex justify-content-center mt-3\"><nav aria-label=\"Volumes pagination\"><ul class=\"pagination pagination-sm mb-0\"><!-- Previous Button -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.CurrentPage > 1 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var33 string
- templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage-1))
+ var templ_7745c5c3_Var34 string
+ templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage-1))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 407, Col: 138}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "\"><i class=\"fas fa-chevron-left\"></i></a></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\"><i class=\"fas fa-chevron-left\"></i></a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-left\"></i></span></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-left\"></i></span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<!-- Page Numbers -->")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<!-- Page Numbers -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i := maxInt(1, data.CurrentPage-2); i <= minInt(data.TotalPages, data.CurrentPage+2); i++ {
if i == data.CurrentPage {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<li class=\"page-item active\"><span class=\"page-link\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<li class=\"page-item active\"><span class=\"page-link\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var34 string
- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
+ var templ_7745c5c3_Var35 string
+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 423, Col: 93}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</span></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var35 string
- templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
+ var templ_7745c5c3_Var36 string
+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 427, Col: 125}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var36 string
- templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
+ var templ_7745c5c3_Var37 string
+ templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 427, Col: 148}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</a></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<!-- Next Button -->")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<!-- Next Button -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.CurrentPage < data.TotalPages {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var37 string
- templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage+1))
+ var templ_7745c5c3_Var38 string
+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage+1))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 435, Col: 138}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "\"><i class=\"fas fa-chevron-right\"></i></a></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\"><i class=\"fas fa-chevron-right\"></i></a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-right\"></i></span></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-right\"></i></span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "</ul></nav></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</ul></nav></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "<div class=\"text-center py-5\"><i class=\"fas fa-database fa-3x text-muted mb-3\"></i><h5 class=\"text-muted\">No Volumes Found</h5><p class=\"text-muted\">No volumes are currently available in the cluster.</p></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "<div class=\"text-center py-5\"><i class=\"fas fa-database fa-3x text-muted mb-3\"></i><h5 class=\"text-muted\">No Volumes Found</h5><p class=\"text-muted\">No volumes are currently available in the cluster.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "</div></div><!-- Last Updated --><div class=\"row\"><div class=\"col-12\"><small class=\"text-muted\"><i class=\"fas fa-clock me-1\"></i> Last updated: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</div></div><!-- Last Updated --><div class=\"row\"><div class=\"col-12\"><small class=\"text-muted\"><i class=\"fas fa-clock me-1\"></i> Last updated: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var38 string
- templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("2006-01-02 15:04:05"))
+ var templ_7745c5c3_Var39 string
+ templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("2006-01-02 15:04:05"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 465, Col: 81}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</small></div></div></div><!-- JavaScript for pagination and sorting --><script>\n // Initialize pagination links when page loads\n document.addEventListener('DOMContentLoaded', function() {\n // Add click handlers to pagination links\n document.querySelectorAll('.pagination-link').forEach(link => {\n link.addEventListener('click', function(e) {\n e.preventDefault();\n const page = this.getAttribute('data-page');\n goToPage(page);\n });\n });\n });\n \n function goToPage(page) {\n const url = new URL(window.location);\n url.searchParams.set('page', page);\n window.location.href = url.toString();\n }\n \n function changePageSize() {\n const pageSize = document.getElementById('pageSizeSelect').value;\n const url = new URL(window.location);\n url.searchParams.set('pageSize', pageSize);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function sortTable(column) {\n const url = new URL(window.location);\n const currentSort = url.searchParams.get('sortBy');\n const currentOrder = url.searchParams.get('sortOrder') || 'asc';\n \n let newOrder = 'asc';\n if (currentSort === column && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n url.searchParams.set('sortBy', column);\n url.searchParams.set('sortOrder', newOrder);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function exportVolumes() {\n // TODO: Implement volume export functionality\n alert('Export functionality to be implemented');\n }\n </script>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</small></div></div></div><!-- JavaScript for pagination and sorting --><script>\n // Initialize pagination links when page loads\n document.addEventListener('DOMContentLoaded', function() {\n // Add click handlers to pagination links\n document.querySelectorAll('.pagination-link').forEach(link => {\n link.addEventListener('click', function(e) {\n e.preventDefault();\n const page = this.getAttribute('data-page');\n goToPage(page);\n });\n });\n \n // Add click handlers to view details buttons\n document.querySelectorAll('.view-details-btn').forEach(button => {\n button.addEventListener('click', function(e) {\n e.preventDefault();\n const volumeId = this.getAttribute('data-volume-id');\n viewVolumeDetails(volumeId);\n });\n });\n });\n \n function goToPage(page) {\n const url = new URL(window.location);\n url.searchParams.set('page', page);\n window.location.href = url.toString();\n }\n \n function changePageSize() {\n const pageSize = document.getElementById('pageSizeSelect').value;\n const url = new URL(window.location);\n url.searchParams.set('pageSize', pageSize);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function sortTable(column) {\n const url = new URL(window.location);\n const currentSort = url.searchParams.get('sortBy');\n const currentOrder = url.searchParams.get('sortOrder') || 'asc';\n \n let newOrder = 'asc';\n if (currentSort === column && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n url.searchParams.set('sortBy', column);\n url.searchParams.set('sortOrder', newOrder);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function exportVolumes() {\n // TODO: Implement volume export functionality\n alert('Export functionality to be implemented');\n }\n \n function viewVolumeDetails(volumeId) {\n // Get the server from the current row\n const button = event.target.closest('button');\n const row = button.closest('tr');\n const serverCell = row.querySelector('td:nth-child(2) a');\n const server = serverCell ? serverCell.textContent.trim() : 'unknown';\n \n window.location.href = `/cluster/volumes/${volumeId}/${encodeURIComponent(server)}`;\n }\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -891,12 +904,12 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
})
}
-func countActiveVolumes(volumes []dash.VolumeInfo) int {
+func countActiveVolumes(volumes []dash.VolumeWithTopology) int {
// Since we removed status tracking, consider all volumes as active
return len(volumes)
}
-func countUniqueDataCenters(volumes []dash.VolumeInfo) int {
+func countUniqueDataCenters(volumes []dash.VolumeWithTopology) int {
dcMap := make(map[string]bool)
for _, volume := range volumes {
dcMap[volume.DataCenter] = true
@@ -904,7 +917,7 @@ func countUniqueDataCenters(volumes []dash.VolumeInfo) int {
return len(dcMap)
}
-func countUniqueRacks(volumes []dash.VolumeInfo) int {
+func countUniqueRacks(volumes []dash.VolumeWithTopology) int {
rackMap := make(map[string]bool)
for _, volume := range volumes {
if volume.Rack != "" {
@@ -914,7 +927,7 @@ func countUniqueRacks(volumes []dash.VolumeInfo) int {
return len(rackMap)
}
-func countUniqueDiskTypes(volumes []dash.VolumeInfo) int {
+func countUniqueDiskTypes(volumes []dash.VolumeWithTopology) int {
diskTypeMap := make(map[string]bool)
for _, volume := range volumes {
diskType := volume.DiskType
@@ -942,23 +955,23 @@ func getSortIcon(column, currentSort, currentOrder string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var39 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var39 == nil {
- templ_7745c5c3_Var39 = templ.NopComponent
+ templ_7745c5c3_Var40 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var40 == nil {
+ templ_7745c5c3_Var40 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if column != currentSort {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "<i class=\"fas fa-sort text-muted ms-1\"></i>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "<i class=\"fas fa-sort text-muted ms-1\"></i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if currentOrder == "asc" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "<i class=\"fas fa-sort-up text-primary ms-1\"></i>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "<i class=\"fas fa-sort-up text-primary ms-1\"></i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "<i class=\"fas fa-sort-down text-primary ms-1\"></i>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "<i class=\"fas fa-sort-down text-primary ms-1\"></i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/weed/admin/view/app/volume_details.templ b/weed/admin/view/app/volume_details.templ
new file mode 100644
index 000000000..01876b46f
--- /dev/null
+++ b/weed/admin/view/app/volume_details.templ
@@ -0,0 +1,425 @@
+package app
+
+import (
+ "fmt"
+ "time"
+ "github.com/seaweedfs/seaweedfs/weed/admin/dash"
+)
+
+templ VolumeDetails(data dash.VolumeDetailsData) {
+ <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-database me-2"></i>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/volumes" class="text-decoration-none">Volumes</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Volume {fmt.Sprintf("%d", data.Volume.Id)}</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>
+
+ <div class="row">
+ <!-- Volume Information Card -->
+ <div class="col-lg-8">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-info-circle me-2"></i>Volume Information
+ </h6>
+ </div>
+ <div class="card-body">
+ <div class="row">
+ <div class="col-md-6">
+ <div class="mb-3">
+ <label class="form-label"><strong>Volume ID:</strong></label>
+ <div><code class="fs-5">{fmt.Sprintf("%d", data.Volume.Id)}</code></div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Server:</strong></label>
+ <div>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))} target="_blank" class="text-decoration-none">
+ {data.Volume.Server}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Data Center:</strong></label>
+ <div><span class="badge bg-light text-dark">{data.Volume.DataCenter}</span></div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Rack:</strong></label>
+ <div><span class="badge bg-light text-dark">{data.Volume.Rack}</span></div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <div class="mb-3">
+ <label class="form-label"><strong>Collection:</strong></label>
+ <div>
+ if data.Volume.Collection == "" {
+ <a href={templ.SafeURL("/cluster/volumes?collection=default")} class="text-decoration-none">
+ <span class="badge bg-secondary">default</span>
+ </a>
+ } else {
+ <a href={templ.SafeURL(fmt.Sprintf("/cluster/volumes?collection=%s", data.Volume.Collection))} class="text-decoration-none">
+ <span class="badge bg-secondary">{data.Volume.Collection}</span>
+ </a>
+ }
+ </div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Replication:</strong></label>
+ <div><span class="badge bg-info">{fmt.Sprintf("%03d", data.Volume.ReplicaPlacement)}</span></div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Disk Type:</strong></label>
+ <div>
+ <span class="badge bg-primary">
+ if data.Volume.DiskType == "" {
+ hdd
+ } else {
+ {data.Volume.DiskType}
+ }
+ </span>
+ </div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Version:</strong></label>
+ <div><span class="badge bg-dark">{fmt.Sprintf("v%d", data.Volume.Version)}</span></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Statistics Card -->
+ <div class="col-lg-4">
+ <!-- Volume Statistics & Health Card -->
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-chart-pie me-2"></i>Volume Statistics & Health
+ </h6>
+ </div>
+ <div class="card-body">
+ <!-- Storage Metrics -->
+ <div class="row mb-3">
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-success">
+ {formatBytes(int64(data.Volume.Size - data.Volume.DeletedByteCount))}
+ </div>
+ <small class="text-muted">Active Bytes</small>
+ </div>
+ </div>
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-danger">
+ {formatBytes(int64(data.Volume.DeletedByteCount))}
+ </div>
+ <small class="text-muted">Deleted Bytes</small>
+ </div>
+ </div>
+ </div>
+
+
+
+ <!-- File Metrics -->
+ <div class="row mb-3">
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-success">
+ {fmt.Sprintf("%d", data.Volume.FileCount)}
+ </div>
+ <small class="text-muted">Active Files</small>
+ </div>
+ </div>
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-danger">
+ {fmt.Sprintf("%d", data.Volume.DeleteCount)}
+ </div>
+ <small class="text-muted">Deleted Files</small>
+ </div>
+ </div>
+ </div>
+
+
+
+ <!-- Storage Efficiency -->
+ if data.Volume.FileCount > 0 && data.Volume.Size > 0 {
+ <div class="mb-3">
+ <div class="d-flex justify-content-between align-items-center mb-1">
+ <small class="text-muted">Storage Efficiency</small>
+ <small class="text-muted">
+ {fmt.Sprintf("%.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)}
+ </small>
+ </div>
+ <div class="progress" style="height: 8px;">
+ <div class="progress-bar bg-info" role="progressbar"
+ style={fmt.Sprintf("width: %.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)}
+ aria-valuenow={fmt.Sprintf("%.1f", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)}
+ aria-valuemin="0" aria-valuemax="100">
+ </div>
+ </div>
+ </div>
+ }
+
+ <hr class="my-3">
+
+ <!-- Status & Configuration -->
+ <div class="row mb-3">
+ <div class="col-12">
+ <div class="text-center mb-2">
+ if data.Volume.ReadOnly {
+ <span class="badge bg-warning fs-6 px-3 py-2">
+ <i class="fas fa-lock me-1"></i>Read Only
+ </span>
+ if data.Volume.Size >= data.VolumeSizeLimit {
+ <div class="mt-1">
+ <small class="text-muted">Size limit exceeded</small>
+ </div>
+ }
+ } else if data.VolumeSizeLimit > data.Volume.Size {
+ <span class="badge bg-success fs-6 px-3 py-2">
+ <i class="fas fa-edit me-1"></i>Read/Write
+ </span>
+ } else {
+ <span class="badge bg-warning fs-6 px-3 py-2">
+ <i class="fas fa-exclamation-triangle me-1"></i>Size Limit Reached
+ </span>
+ }
+ </div>
+ </div>
+ </div>
+
+ <!-- Maintenance Info -->
+ <div class="row mb-3">
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h6 mb-0 font-weight-bold text-info">
+ #{fmt.Sprintf("%d", data.Volume.CompactRevision)}
+ </div>
+ <small class="text-muted">Compact Revision</small>
+ </div>
+ </div>
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h6 mb-0 font-weight-bold text-secondary">
+ if data.Volume.ModifiedAtSecond > 0 {
+ {formatTimestamp(data.Volume.ModifiedAtSecond)}
+ } else {
+ <span class="text-muted">Never modified</span>
+ }
+ </div>
+ <small class="text-muted">Last Modified</small>
+ </div>
+ </div>
+ </div>
+
+ <!-- TTL Configuration -->
+ if data.Volume.Ttl > 0 {
+ <div class="mb-3 text-center">
+ <span class="badge bg-info fs-6 px-3 py-2">
+ <i class="fas fa-clock me-1"></i>{formatTTL(data.Volume.Ttl)}
+ </span>
+ <div class="mt-1">
+ <small class="text-muted">Time To Live</small>
+ </div>
+ </div>
+ }
+
+ <!-- Remote Storage Configuration -->
+ if data.Volume.RemoteStorageName != "" {
+ <hr class="my-3">
+ <div class="mb-2">
+ <div class="text-center">
+ <div class="h6 mb-1 font-weight-bold text-info">
+ <i class="fas fa-cloud me-1"></i>{data.Volume.RemoteStorageName}
+ </div>
+ <small class="text-muted">Remote Storage</small>
+ </div>
+ </div>
+ if data.Volume.RemoteStorageKey != "" {
+ <div class="text-center">
+ <div class="text-xs font-monospace bg-light p-2 rounded text-truncate" title={data.Volume.RemoteStorageKey}>
+ {data.Volume.RemoteStorageKey}
+ </div>
+ <small class="text-muted">Storage Key</small>
+ </div>
+ }
+ }
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Replicas Card -->
+ if len(data.Replicas) > 0 {
+ <div class="row">
+ <div class="col-12">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-copy me-2"></i>Replicas ({fmt.Sprintf("%d", data.ReplicationCount)})
+ </h6>
+ </div>
+ <div class="card-body">
+ <div class="table-responsive">
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th>Server</th>
+ <th>Data Center</th>
+ <th>Rack</th>
+ <th>Size</th>
+ <th>File Count</th>
+ <th>Status</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ <!-- Primary Volume (current one) -->
+ <tr class="table-primary">
+ <td>
+ <strong>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))} target="_blank" class="text-decoration-none">
+ {data.Volume.Server}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </strong>
+ <span class="badge bg-success ms-2">Primary</span>
+ </td>
+ <td><span class="badge bg-light text-dark">{data.Volume.DataCenter}</span></td>
+ <td><span class="badge bg-light text-dark">{data.Volume.Rack}</span></td>
+ <td>{formatBytes(int64(data.Volume.Size))}</td>
+ <td>{fmt.Sprintf("%d", data.Volume.FileCount)}</td>
+ <td><span class="badge bg-success">Active</span></td>
+ <td>
+ <span class="text-muted">Current Volume</span>
+ </td>
+ </tr>
+ <!-- Replica Volumes -->
+ for _, replica := range data.Replicas {
+ <tr>
+ <td>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", replica.Server))} target="_blank" class="text-decoration-none">
+ {replica.Server}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </td>
+ <td><span class="badge bg-light text-dark">{replica.DataCenter}</span></td>
+ <td><span class="badge bg-light text-dark">{replica.Rack}</span></td>
+ <td>{formatBytes(int64(replica.Size))}</td>
+ <td>{fmt.Sprintf("%d", replica.FileCount)}</td>
+ <td><span class="badge bg-info">Replica</span></td>
+ <td>
+ <a href={templ.SafeURL(fmt.Sprintf("/cluster/volumes/%d/%s", replica.Id, replica.Server))} class="btn btn-sm btn-outline-primary">
+ <i class="fas fa-eye me-1"></i>View
+ </a>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ }
+
+ <!-- Actions Card -->
+ <div class="row">
+ <div class="col-12">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-tools me-2"></i>Actions
+ </h6>
+ </div>
+ <div class="card-body">
+ <div class="btn-group" role="group">
+ <button type="button" class="btn btn-outline-secondary" title="Compact Volume">
+ <i class="fas fa-compress-alt me-1"></i>Compact
+ </button>
+ <button type="button" class="btn btn-outline-warning" title="Fix Volume">
+ <i class="fas fa-wrench me-1"></i>Fix
+ </button>
+ <button type="button" class="btn btn-outline-danger" title="Vacuum Volume">
+ <i class="fas fa-trash me-1"></i>Vacuum
+ </button>
+ </div>
+ <div class="mt-3">
+ <small class="text-muted">
+ <i class="fas fa-info-circle me-1"></i>
+ Use these actions to perform maintenance operations on the volume.
+ </small>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Last Updated -->
+ <div class="row">
+ <div class="col-12">
+ <small class="text-muted">
+ <i class="fas fa-clock me-1"></i>
+ Last updated: {data.LastUpdated.Format("2006-01-02 15:04:05")}
+ </small>
+ </div>
+ </div>
+}
+
+func formatTimestamp(unixTimestamp int64) string {
+ if unixTimestamp <= 0 {
+ return "Never"
+ }
+ t := time.Unix(unixTimestamp, 0)
+ return t.Format("2006-01-02 15:04:05")
+}
+
+func formatTTL(ttlSeconds uint32) string {
+ if ttlSeconds == 0 {
+ return "No TTL"
+ }
+
+ duration := time.Duration(ttlSeconds) * time.Second
+
+ // Convert to human readable format
+ days := int(duration.Hours()) / 24
+ hours := int(duration.Hours()) % 24
+ minutes := int(duration.Minutes()) % 60
+
+ if days > 0 {
+ if hours > 0 {
+ return fmt.Sprintf("%dd %dh", days, hours)
+ }
+ return fmt.Sprintf("%d days", days)
+ } else if hours > 0 {
+ if minutes > 0 {
+ return fmt.Sprintf("%dh %dm", hours, minutes)
+ }
+ return fmt.Sprintf("%d hours", hours)
+ } else if minutes > 0 {
+ return fmt.Sprintf("%d minutes", minutes)
+ } else {
+ return fmt.Sprintf("%d seconds", int(duration.Seconds()))
+ }
+} \ No newline at end of file
diff --git a/weed/admin/view/app/volume_details_templ.go b/weed/admin/view/app/volume_details_templ.go
new file mode 100644
index 000000000..6217fa695
--- /dev/null
+++ b/weed/admin/view/app/volume_details_templ.go
@@ -0,0 +1,690 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.833
+package app
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import (
+ "fmt"
+ "github.com/seaweedfs/seaweedfs/weed/admin/dash"
+ "time"
+)
+
+func VolumeDetails(data dash.VolumeDetailsData) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<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-database me-2\"></i>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/volumes\" class=\"text-decoration-none\">Volumes</a></li><li class=\"breadcrumb-item active\" aria-current=\"page\">Volume ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.Id))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 19, Col: 116}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</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><div class=\"row\"><!-- Volume Information Card --><div class=\"col-lg-8\"><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-info-circle me-2\"></i>Volume Information</h6></div><div class=\"card-body\"><div class=\"row\"><div class=\"col-md-6\"><div class=\"mb-3\"><label class=\"form-label\"><strong>Volume ID:</strong></label><div><code class=\"fs-5\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var3 string
+ templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.Id))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 49, Col: 90}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</code></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Server:</strong></label><div><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" target=\"_blank\" class=\"text-decoration-none\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Server)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 55, Col: 59}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " <i class=\"fas fa-external-link-alt ms-1 text-muted\"></i></a></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Data Center:</strong></label><div><span class=\"badge bg-light text-dark\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var6 string
+ templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.DataCenter)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 62, Col: 99}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Rack:</strong></label><div><span class=\"badge bg-light text-dark\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var7 string
+ templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Rack)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 66, Col: 93}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</span></div></div></div><div class=\"col-md-6\"><div class=\"mb-3\"><label class=\"form-label\"><strong>Collection:</strong></label><div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.Collection == "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 templ.SafeURL = templ.SafeURL("/cluster/volumes?collection=default")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"text-decoration-none\"><span class=\"badge bg-secondary\">default</span></a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var9 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/cluster/volumes?collection=%s", data.Volume.Collection))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" class=\"text-decoration-none\"><span class=\"badge bg-secondary\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var10 string
+ templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Collection)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 79, Col: 100}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</span></a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Replication:</strong></label><div><span class=\"badge bg-info\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var11 string
+ templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%03d", data.Volume.ReplicaPlacement))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 86, Col: 115}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</span></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Disk Type:</strong></label><div><span class=\"badge bg-primary\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.DiskType == "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "hdd")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ var templ_7745c5c3_Var12 string
+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.DiskType)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 95, Col: 65}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</span></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Version:</strong></label><div><span class=\"badge bg-dark\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var13 string
+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("v%d", data.Volume.Version))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 102, Col: 105}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</span></div></div></div></div></div></div></div><!-- Statistics Card --><div class=\"col-lg-4\"><!-- Volume Statistics & Health Card --><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-chart-pie me-2\"></i>Volume Statistics & Health</h6></div><div class=\"card-body\"><!-- Storage Metrics --><div class=\"row mb-3\"><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-success\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var14 string
+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(data.Volume.Size - data.Volume.DeletedByteCount)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 125, Col: 104}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div><small class=\"text-muted\">Active Bytes</small></div></div><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-danger\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var15 string
+ templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(data.Volume.DeletedByteCount)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 133, Col: 85}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</div><small class=\"text-muted\">Deleted Bytes</small></div></div></div><!-- File Metrics --><div class=\"row mb-3\"><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-success\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var16 string
+ templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.FileCount))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 147, Col: 77}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</div><small class=\"text-muted\">Active Files</small></div></div><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-danger\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var17 string
+ templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.DeleteCount))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 155, Col: 79}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</div><small class=\"text-muted\">Deleted Files</small></div></div></div><!-- Storage Efficiency -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.FileCount > 0 && data.Volume.Size > 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<div class=\"mb-3\"><div class=\"d-flex justify-content-between align-items-center mb-1\"><small class=\"text-muted\">Storage Efficiency</small> <small class=\"text-muted\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var18 string
+ templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 170, Col: 144}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</small></div><div class=\"progress\" style=\"height: 8px;\"><div class=\"progress-bar bg-info\" role=\"progressbar\" style=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var19 string
+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("width: %.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 175, Col: 158}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" aria-valuenow=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var20 string
+ templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 176, Col: 157}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<hr class=\"my-3\"><!-- Status & Configuration --><div class=\"row mb-3\"><div class=\"col-12\"><div class=\"text-center mb-2\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.ReadOnly {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<span class=\"badge bg-warning fs-6 px-3 py-2\"><i class=\"fas fa-lock me-1\"></i>Read Only</span> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.Size >= data.VolumeSizeLimit {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"mt-1\"><small class=\"text-muted\">Size limit exceeded</small></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ } else if data.VolumeSizeLimit > data.Volume.Size {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<span class=\"badge bg-success fs-6 px-3 py-2\"><i class=\"fas fa-edit me-1\"></i>Read/Write</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<span class=\"badge bg-warning fs-6 px-3 py-2\"><i class=\"fas fa-exclamation-triangle me-1\"></i>Size Limit Reached</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</div></div></div><!-- Maintenance Info --><div class=\"row mb-3\"><div class=\"col-6\"><div class=\"text-center\"><div class=\"h6 mb-0 font-weight-bold text-info\">#")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var21 string
+ templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.CompactRevision))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 216, Col: 84}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</div><small class=\"text-muted\">Compact Revision</small></div></div><div class=\"col-6\"><div class=\"text-center\"><div class=\"h6 mb-0 font-weight-bold text-secondary\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.ModifiedAtSecond > 0 {
+ var templ_7745c5c3_Var22 string
+ templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(formatTimestamp(data.Volume.ModifiedAtSecond))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 225, Col: 86}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<span class=\"text-muted\">Never modified</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div><small class=\"text-muted\">Last Modified</small></div></div></div><!-- TTL Configuration -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.Ttl > 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div class=\"mb-3 text-center\"><span class=\"badge bg-info fs-6 px-3 py-2\"><i class=\"fas fa-clock me-1\"></i>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var23 string
+ templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(formatTTL(data.Volume.Ttl))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 239, Col: 92}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</span><div class=\"mt-1\"><small class=\"text-muted\">Time To Live</small></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<!-- Remote Storage Configuration -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.RemoteStorageName != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<hr class=\"my-3\"><div class=\"mb-2\"><div class=\"text-center\"><div class=\"h6 mb-1 font-weight-bold text-info\"><i class=\"fas fa-cloud me-1\"></i>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var24 string
+ templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.RemoteStorageName)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 253, Col: 99}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</div><small class=\"text-muted\">Remote Storage</small></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.RemoteStorageKey != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<div class=\"text-center\"><div class=\"text-xs font-monospace bg-light p-2 rounded text-truncate\" title=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var25 string
+ templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.RemoteStorageKey)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 260, Col: 138}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var26 string
+ templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.RemoteStorageKey)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 261, Col: 65}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</div><small class=\"text-muted\">Storage Key</small></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</div></div></div></div><!-- Replicas Card -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(data.Replicas) > 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"row\"><div class=\"col-12\"><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-copy me-2\"></i>Replicas (")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var27 string
+ templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.ReplicationCount))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 279, Col: 111}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, ")</h6></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-hover\"><thead><tr><th>Server</th><th>Data Center</th><th>Rack</th><th>Size</th><th>File Count</th><th>Status</th><th>Actions</th></tr></thead> <tbody><!-- Primary Volume (current one) --><tr class=\"table-primary\"><td><strong><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var28 templ.SafeURL = templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var28)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\" target=\"_blank\" class=\"text-decoration-none\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var29 string
+ templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Server)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 302, Col: 71}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, " <i class=\"fas fa-external-link-alt ms-1 text-muted\"></i></a></strong> <span class=\"badge bg-success ms-2\">Primary</span></td><td><span class=\"badge bg-light text-dark\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var30 string
+ templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.DataCenter)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 308, Col: 106}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</span></td><td><span class=\"badge bg-light text-dark\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var31 string
+ templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Rack)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 309, Col: 100}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</span></td><td>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var32 string
+ templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(data.Volume.Size)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 310, Col: 81}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</td><td>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var33 string
+ templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.FileCount))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 311, Col: 85}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</td><td><span class=\"badge bg-success\">Active</span></td><td><span class=\"text-muted\">Current Volume</span></td></tr><!-- Replica Volumes -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, replica := range data.Replicas {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<tr><td><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var34 templ.SafeURL = templ.SafeURL(fmt.Sprintf("http://%s", replica.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var34)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" target=\"_blank\" class=\"text-decoration-none\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var35 string
+ templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(replica.Server)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 322, Col: 67}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, " <i class=\"fas fa-external-link-alt ms-1 text-muted\"></i></a></td><td><span class=\"badge bg-light text-dark\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var36 string
+ templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(replica.DataCenter)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 326, Col: 106}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</span></td><td><span class=\"badge bg-light text-dark\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var37 string
+ templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(replica.Rack)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 327, Col: 100}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</span></td><td>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var38 string
+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(replica.Size)))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 328, Col: 81}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</td><td>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var39 string
+ templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", replica.FileCount))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 329, Col: 85}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</td><td><span class=\"badge bg-info\">Replica</span></td><td><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var40 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/cluster/volumes/%d/%s", replica.Id, replica.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var40)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" class=\"btn btn-sm btn-outline-primary\"><i class=\"fas fa-eye me-1\"></i>View</a></td></tr>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</tbody></table></div></div></div></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<!-- Actions Card --><div class=\"row\"><div class=\"col-12\"><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-tools me-2\"></i>Actions</h6></div><div class=\"card-body\"><div class=\"btn-group\" role=\"group\"><button type=\"button\" class=\"btn btn-outline-secondary\" title=\"Compact Volume\"><i class=\"fas fa-compress-alt me-1\"></i>Compact</button> <button type=\"button\" class=\"btn btn-outline-warning\" title=\"Fix Volume\"><i class=\"fas fa-wrench me-1\"></i>Fix</button> <button type=\"button\" class=\"btn btn-outline-danger\" title=\"Vacuum Volume\"><i class=\"fas fa-trash me-1\"></i>Vacuum</button></div><div class=\"mt-3\"><small class=\"text-muted\"><i class=\"fas fa-info-circle me-1\"></i> Use these actions to perform maintenance operations on the volume.</small></div></div></div></div></div><!-- Last Updated --><div class=\"row\"><div class=\"col-12\"><small class=\"text-muted\"><i class=\"fas fa-clock me-1\"></i> Last updated: ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var41 string
+ templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("2006-01-02 15:04:05"))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 384, Col: 77}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</small></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func formatTimestamp(unixTimestamp int64) string {
+ if unixTimestamp <= 0 {
+ return "Never"
+ }
+ t := time.Unix(unixTimestamp, 0)
+ return t.Format("2006-01-02 15:04:05")
+}
+
+func formatTTL(ttlSeconds uint32) string {
+ if ttlSeconds == 0 {
+ return "No TTL"
+ }
+
+ duration := time.Duration(ttlSeconds) * time.Second
+
+ // Convert to human readable format
+ days := int(duration.Hours()) / 24
+ hours := int(duration.Hours()) % 24
+ minutes := int(duration.Minutes()) % 60
+
+ if days > 0 {
+ if hours > 0 {
+ return fmt.Sprintf("%dd %dh", days, hours)
+ }
+ return fmt.Sprintf("%d days", days)
+ } else if hours > 0 {
+ if minutes > 0 {
+ return fmt.Sprintf("%dh %dm", hours, minutes)
+ }
+ return fmt.Sprintf("%d hours", hours)
+ } else if minutes > 0 {
+ return fmt.Sprintf("%d minutes", minutes)
+ } else {
+ return fmt.Sprintf("%d seconds", int(duration.Seconds()))
+ }
+}
+
+var _ = templruntime.GeneratedTemplate