aboutsummaryrefslogtreecommitdiff
path: root/weed/admin/view/app/admin.templ
diff options
context:
space:
mode:
authorChris Lu <chrislusf@users.noreply.github.com>2025-07-01 01:28:09 -0700
committerGitHub <noreply@github.com>2025-07-01 01:28:09 -0700
commit1defee3d682d86c7e0cbc7db7ebdb9cae872a471 (patch)
treedae266bee79c36a74214a47d3d9e9274b322d49d /weed/admin/view/app/admin.templ
parente5adc3872a79e062826a387e1e2bb68196f14014 (diff)
downloadseaweedfs-1defee3d682d86c7e0cbc7db7ebdb9cae872a471.tar.xz
seaweedfs-1defee3d682d86c7e0cbc7db7ebdb9cae872a471.zip
Add admin component (#6928)
* init version * relocate * add s3 bucket link * refactor handlers into weed/admin folder * fix login logout * adding favicon * remove fall back to http get topology * grpc dial option, disk total capacity * show filer count * fix each volume disk usage * add filers to dashboard * adding hosts, volumes, collections * refactor code and menu * remove "refresh" button * fix data for collections * rename cluster hosts into volume servers * add masters, filers * reorder * adding file browser * create folder and upload files * add filer version, created at time * remove mock data * remove fields * fix submenu item highlighting * fix bucket creation * purge files * delete multiple * fix bucket creation * remove region from buckets * add object store with buckets and users * rendering permission * refactor * get bucket objects and size * link to file browser * add file size and count for collections page * paginate the volumes * fix possible SSRF https://github.com/seaweedfs/seaweedfs/pull/6928/checks?check_run_id=45108469801 * Update weed/command/admin.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update weed/command/admin.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix build * import * remove filer CLI option * remove filer option * remove CLI options --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Diffstat (limited to 'weed/admin/view/app/admin.templ')
-rw-r--r--weed/admin/view/app/admin.templ351
1 files changed, 351 insertions, 0 deletions
diff --git a/weed/admin/view/app/admin.templ b/weed/admin/view/app/admin.templ
new file mode 100644
index 000000000..ceb11b0f2
--- /dev/null
+++ b/weed/admin/view/app/admin.templ
@@ -0,0 +1,351 @@
+package app
+
+import (
+ "fmt"
+ "github.com/seaweedfs/seaweedfs/weed/admin/dash"
+)
+
+templ Admin(data dash.AdminData) {
+ <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+ <h1 class="h2">
+ <i class="fas fa-tachometer-alt me-2"></i>Dashboard
+ </h1>
+ <div class="btn-toolbar mb-2 mb-md-0">
+ <div class="btn-group me-2">
+ <a href="/s3/buckets" class="btn btn-sm btn-primary">
+ <i class="fas fa-cube me-1"></i>S3 Buckets
+ </a>
+
+ </div>
+ </div>
+ </div>
+
+ <div id="dashboard-content">
+ <!-- Status Cards -->
+ <div class="row mb-4">
+ <div class="col-xl-3 col-md-6 mb-4">
+ <div class="card border-left-primary shadow h-100 py-2">
+ <div class="card-body">
+ <div class="row no-gutters align-items-center">
+ <div class="col mr-2">
+ <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
+ Cluster Status
+ </div>
+ <div class="h5 mb-0 font-weight-bold text-gray-800">
+ <span class={fmt.Sprintf("badge bg-%s", getStatusColor(data.ClusterStatus))}>
+ {data.ClusterStatus}
+ </span>
+ </div>
+ </div>
+ <div class="col-auto">
+ <i class="fas fa-heartbeat fa-2x text-gray-300"></i>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-xl-3 col-md-6 mb-4">
+ <div class="card border-left-success shadow h-100 py-2">
+ <div class="card-body">
+ <div class="row no-gutters align-items-center">
+ <div class="col mr-2">
+ <div class="text-xs font-weight-bold text-success text-uppercase mb-1">
+ Total Volumes
+ </div>
+ <div class="h5 mb-0 font-weight-bold text-gray-800">
+ {fmt.Sprintf("%d", data.TotalVolumes)}
+ </div>
+ </div>
+ <div class="col-auto">
+ <i class="fas fa-database fa-2x text-gray-300"></i>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-xl-3 col-md-6 mb-4">
+ <div class="card border-left-info shadow h-100 py-2">
+ <div class="card-body">
+ <div class="row no-gutters align-items-center">
+ <div class="col mr-2">
+ <div class="text-xs font-weight-bold text-info text-uppercase mb-1">
+ Total Files
+ </div>
+ <div class="h5 mb-0 font-weight-bold text-gray-800">
+ {formatNumber(data.TotalFiles)}
+ </div>
+ </div>
+ <div class="col-auto">
+ <i class="fas fa-file fa-2x text-gray-300"></i>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-xl-3 col-md-6 mb-4">
+ <div class="card border-left-warning shadow h-100 py-2">
+ <div class="card-body">
+ <div class="row no-gutters align-items-center">
+ <div class="col mr-2">
+ <div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
+ Total Size
+ </div>
+ <div class="h5 mb-0 font-weight-bold text-gray-800">
+ {formatBytes(data.TotalSize)}
+ </div>
+ </div>
+ <div class="col-auto">
+ <i class="fas fa-hdd fa-2x text-gray-300"></i>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Master Nodes Status -->
+ <div class="row mb-4">
+ <div class="col-lg-6">
+ <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-server me-2"></i>Master Nodes
+ </h6>
+ </div>
+ <div class="card-body">
+ <div class="table-responsive">
+ <table class="table table-bordered" width="100%" cellspacing="0">
+ <thead>
+ <tr>
+ <th>Address</th>
+ <th>Role</th>
+ <th>Status</th>
+ </tr>
+ </thead>
+ <tbody>
+ for _, master := range data.MasterNodes {
+ <tr>
+ <td>{master.Address}</td>
+ <td>
+ if master.IsLeader {
+ <span class="badge bg-primary">Leader</span>
+ } else {
+ <span class="badge bg-secondary">Follower</span>
+ }
+ </td>
+ <td>
+ <span class={fmt.Sprintf("badge bg-%s", getStatusColor(master.Status))}>
+ {master.Status}
+ </span>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- System Health -->
+ <div class="col-lg-6">
+ <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>System Health
+ </h6>
+ </div>
+ <div class="card-body text-center">
+ <div class="mb-3">
+ <h3 class={fmt.Sprintf("text-%s", getHealthColor(data.SystemHealth))}>
+ {data.SystemHealth}
+ </h3>
+ </div>
+ <div class="row">
+ <div class="col-4">
+ <div class="card bg-light">
+ <div class="card-body">
+ <h5>{fmt.Sprintf("%d", len(data.MasterNodes))}</h5>
+ <small class="text-muted">Masters</small>
+ </div>
+ </div>
+ </div>
+ <div class="col-4">
+ <div class="card bg-light">
+ <div class="card-body">
+ <h5>{fmt.Sprintf("%d", len(data.VolumeServers))}</h5>
+ <small class="text-muted">Volume Servers</small>
+ </div>
+ </div>
+ </div>
+ <div class="col-4">
+ <div class="card bg-light">
+ <div class="card-body">
+ <h5>{fmt.Sprintf("%d", len(data.FilerNodes))}</h5>
+ <small class="text-muted">Filers</small>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Volume Servers -->
+ <div class="row">
+ <div class="col-12">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-database me-2"></i>Volume Servers
+ </h6>
+ <div class="dropdown no-arrow">
+ <a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
+ <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
+ </a>
+ <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in">
+ <div class="dropdown-header">Actions:</div>
+ <a class="dropdown-item" href="/volumes">View Details</a>
+ <a class="dropdown-item" href="/cluster">Topology View</a>
+ </div>
+ </div>
+ </div>
+ <div class="card-body">
+ <div class="table-responsive">
+ <table class="table table-hover" width="100%" cellspacing="0">
+ <thead>
+ <tr>
+ <th>ID</th>
+ <th>Address</th>
+ <th>Data Center</th>
+ <th>Rack</th>
+ <th>Volumes</th>
+ <th>Capacity</th>
+ <th>Status</th>
+ </tr>
+ </thead>
+ <tbody>
+ for _, vs := range data.VolumeServers {
+ <tr>
+ <td>{vs.ID}</td>
+ <td>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", vs.PublicURL))} target="_blank">
+ {vs.Address}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </td>
+ <td>{vs.DataCenter}</td>
+ <td>{vs.Rack}</td>
+ <td>
+ <div class="progress" style="height: 20px;">
+ <div class="progress-bar" role="progressbar"
+ style={fmt.Sprintf("width: %d%%", calculatePercent(vs.Volumes, vs.MaxVolumes))}>
+ {fmt.Sprintf("%d/%d", vs.Volumes, vs.MaxVolumes)}
+ </div>
+ </div>
+ </td>
+ <td>{formatBytes(vs.DiskUsage)} / {formatBytes(vs.DiskCapacity)}</td>
+ <td>
+ <span class={fmt.Sprintf("badge bg-%s", getStatusColor(vs.Status))}>
+ {vs.Status}
+ </span>
+ </td>
+ </tr>
+ }
+ if len(data.VolumeServers) == 0 {
+ <tr>
+ <td colspan="7" class="text-center text-muted py-4">
+ <i class="fas fa-info-circle me-2"></i>
+ No volume servers found
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Filer Nodes -->
+ <div class="row mb-4">
+ <div class="col-12">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-folder me-2"></i>Filer Nodes
+ </h6>
+ <div class="dropdown no-arrow">
+ <a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
+ <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
+ </a>
+ <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in">
+ <div class="dropdown-header">Actions:</div>
+ <a class="dropdown-item" href="/filer">File Browser</a>
+ <a class="dropdown-item" href="/cluster">Topology View</a>
+ </div>
+ </div>
+ </div>
+ <div class="card-body">
+ <div class="table-responsive">
+ <table class="table table-hover" width="100%" cellspacing="0">
+ <thead>
+ <tr>
+ <th>Address</th>
+ <th>Data Center</th>
+ <th>Rack</th>
+ <th>Status</th>
+ <th>Last Updated</th>
+ </tr>
+ </thead>
+ <tbody>
+ for _, filer := range data.FilerNodes {
+ <tr>
+ <td>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", filer.Address))} target="_blank">
+ {filer.Address}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </td>
+ <td>{filer.DataCenter}</td>
+ <td>{filer.Rack}</td>
+ <td>
+ <span class={fmt.Sprintf("badge bg-%s", getStatusColor(filer.Status))}>
+ {filer.Status}
+ </span>
+ </td>
+ <td>{filer.LastUpdated.Format("2006-01-02 15:04:05")}</td>
+ </tr>
+ }
+ if len(data.FilerNodes) == 0 {
+ <tr>
+ <td colspan="5" class="text-center text-muted py-4">
+ <i class="fas fa-info-circle me-2"></i>
+ No filer nodes found
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </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>
+ </div>
+} \ No newline at end of file