package main
import (
"encoding/json"
"math/rand"
"net/http"
"sync"
"time"
"github.com/go-via/via"
"github.com/go-via/via/h"
)
type Host struct {
Name string `json:"name"`
Status string `json:"status"`
IPAddress string `json:"ip"`
Version string `json:"version"`
CPUs int `json:"cpus"`
MemoryGB int `json:"memoryGB"`
StorageGB int `json:"storageGB"`
}
var (
hosts = []Host{
{Name: "ESXi-01", Status: "运行中", IPAddress: "192.168.1.101", Version: "7.0 U3", CPUs: 32, MemoryGB: 256, StorageGB: 2048},
{Name: "ESXi-02", Status: "运行中", IPAddress: "192.168.1.102", Version: "7.0 U3", CPUs: 24, MemoryGB: 192, StorageGB: 1536},
{Name: "ESXi-03", Status: "维护中", IPAddress: "192.168.1.103", Version: "6.7 U3", CPUs: 16, MemoryGB: 128, StorageGB: 1024},
{Name: "ESXi-04", Status: "运行中", IPAddress: "192.168.1.104", Version: "8.0 U1", CPUs: 48, MemoryGB: 512, StorageGB: 4096},
{Name: "ESXi-05", Status: "已关机", IPAddress: "192.168.1.105", Version: "7.0 U2", CPUs: 20, MemoryGB: 128, StorageGB: 1024},
}
hostsMutex sync.Mutex
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
)
type Page struct {
}
func (p *Page) View(ctx *via.CtxR) h.H {
return h.Div(h.Class("container"),
h.H1(h.Text("ESXi 主机管理面板")),
h.Div(h.ID("myGrid"), h.Class("ag-theme-alpine")),
)
}
func main() {
app := via.New(
via.WithTitle("ESXi 主机管理面板"),
via.WithInsecureCookies(),
)
app.AppendToHead(h.StyleEl(h.Raw(`
body { margin: 0; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; }
.container { max-width: 1400px; margin: 0 auto; }
h1 { color: #333; margin-bottom: 20px; }
#myGrid { height: 600px; width: 100%; border-radius: 8px; box-shadow: 0 2px 12px rgba(0,0,0,0.1); }
.status-running { color: #10b981; font-weight: 600; }
.status-maintenance { color: #f59e0b; font-weight: 600; }
.status-offline { color: #ef4444; font-weight: 600; }
`)))
app.AppendToHead(h.Link(h.Rel("stylesheet"), h.Href("https://esm.sh/ag-grid-community@36/styles/ag-theme-alpine.min.css")))
app.AppendToHead(h.Script(h.Type("importmap"), h.Raw(`
{
"imports": {
"ag-grid-community": "https://esm.sh/ag-grid-community@36"
}
}
`)))
app.AppendToFoot(h.Script(h.Type("module"), h.Raw(`
import { createGrid, ModuleRegistry, AllCommunityModule } from 'ag-grid-community';
ModuleRegistry.registerModules([AllCommunityModule]);
const columnDefs = [
{ headerName: '主机名', field: 'name', sortable: true, filter: true, width: 120 },
{
headerName: '状态',
field: 'status',
sortable: true,
filter: true,
width: 100,
cellClass: params => {
if (params.value === '运行中') return 'status-running';
if (params.value === '维护中') return 'status-maintenance';
return 'status-offline';
}
},
{ headerName: 'IP 地址', field: 'ip', sortable: true, filter: true, width: 140 },
{ headerName: '版本', field: 'version', sortable: true, filter: true, width: 100 },
{ headerName: 'CPU 核心', field: 'cpus', sortable: true, filter: true, width: 100 },
{ headerName: '内存 (GB)', field: 'memoryGB', sortable: true, filter: true, width: 120 },
{ headerName: '存储 (GB)', field: 'storageGB', sortable: true, filter: true, width: 120 }
];
const gridOptions = {
columnDefs: columnDefs,
rowData: [],
pagination: true,
paginationPageSize: 10,
paginationPageSizeSelector: [10, 20, 50, 100],
rowSelection: 'multiple',
getRowId: params => params.data.name,
defaultColDef: {
resizable: true,
filter: true,
sortable: true
},
onGridReady: async (params) => {
let isFirstLoad = true;
const fetchData = async () => {
try {
const response = await fetch('/api/hosts');
const data = await response.json();
if (isFirstLoad) {
params.api.applyTransaction({ add: data });
isFirstLoad = false;
} else {
params.api.applyTransaction({ update: data });
}
} catch (error) {}
};
await fetchData();
setInterval(fetchData, 5000);
}
};
createGrid(document.getElementById('myGrid'), gridOptions);
`)))
app.HandleFunc("GET /api/hosts", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
hostsMutex.Lock()
defer hostsMutex.Unlock()
result := make([]Host, len(hosts))
for i, host := range hosts {
result[i] = host
if rng.Float64() < 0.3 {
statuses := []string{"运行中", "维护中", "已关机"}
result[i].Status = statuses[rng.Intn(len(statuses))]
}
result[i].CPUs = host.CPUs + rng.Intn(5) - 2
if result[i].CPUs < 1 {
result[i].CPUs = 1
}
result[i].MemoryGB = host.MemoryGB + rng.Intn(17) - 8
if result[i].MemoryGB < 1 {
result[i].MemoryGB = 1
}
result[i].StorageGB = host.StorageGB + rng.Intn(101) - 50
if result[i].StorageGB < 1 {
result[i].StorageGB = 1
}
}
json.NewEncoder(w).Encode(result)
})
via.Mount[Page](app, "/")
if err := http.ListenAndServe(":8081", app); err != nil {
panic(err)
}
}