generated from sendnrw/template_golang
init
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
build-binaries / publish-agent (push) Has been skipped
All checks were successful
build-binaries / build (, amd64, linux) (push) Has been skipped
build-binaries / build (, arm, 7, linux) (push) Has been skipped
build-binaries / build (, arm64, linux) (push) Has been skipped
build-binaries / build (.exe, amd64, windows) (push) Has been skipped
build-binaries / release (push) Has been skipped
build-binaries / publish-agent (push) Has been skipped
This commit is contained in:
16
go.mod
Normal file
16
go.mod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module git.send.nrw/sendnrw/pcinfo
|
||||||
|
|
||||||
|
go 1.24.1
|
||||||
|
|
||||||
|
require github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||||
|
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
)
|
||||||
36
go.sum
Normal file
36
go.sum
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
|
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
398
main.go
Normal file
398
main.go
Normal file
@@ -0,0 +1,398 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"html/template"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/shirou/gopsutil/v3/cpu"
|
||||||
|
"github.com/shirou/gopsutil/v3/disk"
|
||||||
|
"github.com/shirou/gopsutil/v3/host"
|
||||||
|
"github.com/shirou/gopsutil/v3/mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetInterface struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
MAC string `json:"mac"`
|
||||||
|
Addresses []string `json:"addresses"`
|
||||||
|
IsLoopback bool `json:"is_loopback"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiskInfo struct {
|
||||||
|
Device string `json:"device"`
|
||||||
|
Mountpoint string `json:"mountpoint"`
|
||||||
|
Fstype string `json:"fstype"`
|
||||||
|
Total uint64 `json:"total"`
|
||||||
|
Used uint64 `json:"used"`
|
||||||
|
Free uint64 `json:"free"`
|
||||||
|
UsedPercent float64 `json:"used_percent"`
|
||||||
|
ReadOnly bool `json:"read_only"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Summary struct {
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Hostname string `json:"hostname"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
OS string `json:"os"`
|
||||||
|
Platform string `json:"platform"`
|
||||||
|
PlatformVersion string `json:"platform_version"`
|
||||||
|
KernelVersion string `json:"kernel_version"`
|
||||||
|
Arch string `json:"arch"`
|
||||||
|
UptimeSeconds uint64 `json:"uptime_seconds"`
|
||||||
|
BootTime time.Time `json:"boot_time"`
|
||||||
|
Interfaces []NetInterface `json:"interfaces"`
|
||||||
|
CPUModel string `json:"cpu_model"`
|
||||||
|
PhysicalCores int `json:"physical_cores"`
|
||||||
|
LogicalCores int `json:"logical_cores"`
|
||||||
|
CPUPercent float64 `json:"cpu_percent"`
|
||||||
|
MemoryTotal uint64 `json:"memory_total"`
|
||||||
|
MemoryUsed uint64 `json:"memory_used"`
|
||||||
|
MemoryFree uint64 `json:"memory_free"`
|
||||||
|
MemoryUsedPct float64 `json:"memory_used_percent"`
|
||||||
|
SwapTotal uint64 `json:"swap_total"`
|
||||||
|
SwapUsed uint64 `json:"swap_used"`
|
||||||
|
SwapFree uint64 `json:"swap_free"`
|
||||||
|
SwapUsedPct float64 `json:"swap_used_percent"`
|
||||||
|
Disks []DiskInfo `json:"disks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mu sync.RWMutex
|
||||||
|
lastCPUUsage float64
|
||||||
|
)
|
||||||
|
|
||||||
|
// background sampler for CPU percentage to avoid blocking API calls
|
||||||
|
func startCPUSampler() {
|
||||||
|
go func() {
|
||||||
|
// Prime once
|
||||||
|
_, _ = cpu.Percent(0, false)
|
||||||
|
for {
|
||||||
|
pcts, err := cpu.Percent(time.Second, false)
|
||||||
|
if err == nil && len(pcts) > 0 {
|
||||||
|
mu.Lock()
|
||||||
|
lastCPUUsage = pcts[0]
|
||||||
|
mu.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInterfaces() []NetInterface {
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var out []NetInterface
|
||||||
|
for _, ifc := range ifaces {
|
||||||
|
// Skip interfaces that are down
|
||||||
|
if (ifc.Flags & net.FlagUp) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addrs, _ := ifc.Addrs()
|
||||||
|
var ips []string
|
||||||
|
isLoop := (ifc.Flags & net.FlagLoopback) != 0
|
||||||
|
for _, a := range addrs {
|
||||||
|
var ip net.IP
|
||||||
|
switch v := a.(type) {
|
||||||
|
case *net.IPNet:
|
||||||
|
ip = v.IP
|
||||||
|
case *net.IPAddr:
|
||||||
|
ip = v.IP
|
||||||
|
}
|
||||||
|
if ip == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ip.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ips = append(ips, ip.String())
|
||||||
|
}
|
||||||
|
// keep even if no IPs, to show MAC etc., but skip pure loopback
|
||||||
|
if isLoop {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, NetInterface{
|
||||||
|
Name: ifc.Name,
|
||||||
|
MAC: ifc.HardwareAddr.String(),
|
||||||
|
Addresses: ips,
|
||||||
|
IsLoopback: isLoop,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDisks() []DiskInfo {
|
||||||
|
parts, err := disk.Partitions(true)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var out []DiskInfo
|
||||||
|
for _, p := range parts {
|
||||||
|
du, err := disk.Usage(p.Mountpoint)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// read-only erkennen (gopsutil: Opts kann string ODER []string sein)
|
||||||
|
ro := false
|
||||||
|
switch opts := any(p.Opts).(type) {
|
||||||
|
case string:
|
||||||
|
ro = strings.Contains(strings.ToLower(opts), "ro")
|
||||||
|
case []string:
|
||||||
|
for _, o := range opts {
|
||||||
|
if strings.EqualFold(o, "ro") {
|
||||||
|
ro = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, DiskInfo{
|
||||||
|
Device: p.Device,
|
||||||
|
Mountpoint: p.Mountpoint,
|
||||||
|
Fstype: p.Fstype,
|
||||||
|
Total: du.Total,
|
||||||
|
Used: du.Used,
|
||||||
|
Free: du.Free,
|
||||||
|
UsedPercent: du.UsedPercent,
|
||||||
|
ReadOnly: ro,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectSummary() Summary {
|
||||||
|
hi, _ := host.Info()
|
||||||
|
vm, _ := mem.VirtualMemory()
|
||||||
|
sw, _ := mem.SwapMemory()
|
||||||
|
coresPhys, _ := cpu.Counts(false)
|
||||||
|
coresLog, _ := cpu.Counts(true)
|
||||||
|
cpuInfos, _ := cpu.Info()
|
||||||
|
username := ""
|
||||||
|
if u, err := user.Current(); err == nil {
|
||||||
|
username = u.Username
|
||||||
|
} else {
|
||||||
|
username = os.Getenv("USERNAME")
|
||||||
|
}
|
||||||
|
model := ""
|
||||||
|
if len(cpuInfos) > 0 {
|
||||||
|
model = cpuInfos[0].ModelName
|
||||||
|
}
|
||||||
|
|
||||||
|
mu.RLock()
|
||||||
|
cpuPct := lastCPUUsage
|
||||||
|
mu.RUnlock()
|
||||||
|
|
||||||
|
var boot time.Time
|
||||||
|
if hi != nil && hi.BootTime > 0 {
|
||||||
|
boot = time.Unix(int64(hi.BootTime), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Summary{
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Hostname: safe(hi, func(h *host.InfoStat) string { return h.Hostname }),
|
||||||
|
Username: username,
|
||||||
|
OS: safe(hi, func(h *host.InfoStat) string { return h.OS }), // "windows"
|
||||||
|
Platform: safe(hi, func(h *host.InfoStat) string { return h.Platform }),
|
||||||
|
PlatformVersion: safe(hi, func(h *host.InfoStat) string { return h.PlatformVersion }),
|
||||||
|
KernelVersion: safe(hi, func(h *host.InfoStat) string { return h.KernelVersion }),
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
UptimeSeconds: safe(hi, func(h *host.InfoStat) uint64 { return h.Uptime }),
|
||||||
|
BootTime: boot,
|
||||||
|
Interfaces: getInterfaces(),
|
||||||
|
CPUModel: model,
|
||||||
|
PhysicalCores: coresPhys,
|
||||||
|
LogicalCores: coresLog,
|
||||||
|
CPUPercent: cpuPct,
|
||||||
|
MemoryTotal: vm.Total,
|
||||||
|
MemoryUsed: vm.Used,
|
||||||
|
MemoryFree: vm.Free,
|
||||||
|
MemoryUsedPct: vm.UsedPercent,
|
||||||
|
SwapTotal: sw.Total,
|
||||||
|
SwapUsed: sw.Used,
|
||||||
|
SwapFree: sw.Free,
|
||||||
|
SwapUsedPct: sw.UsedPercent,
|
||||||
|
Disks: getDisks(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func safe[T any, R any](v T, f func(T) R) R {
|
||||||
|
if any(v) == nil {
|
||||||
|
var zero R
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
return f(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
var page = template.Must(template.New("index").Parse(`<!doctype html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
|
<title>Windows Systeminfo</title>
|
||||||
|
<style>
|
||||||
|
:root { --bg:#0b1220; --fg:#e7eefc; --muted:#a3b1cc; --card:#111a2e; --accent:#5aa9ff; }
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{max-width:980px;margin:0 auto;padding:24px;font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;background:var(--bg);color:var(--fg)}
|
||||||
|
h1{font-size:22px;margin:0 0 16px 0}
|
||||||
|
.grid{display:flex;flex-direction:column;gap:12px}
|
||||||
|
.card{width:100%}
|
||||||
|
.k{font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
|
||||||
|
.v{font-weight:600}
|
||||||
|
.row{display:flex;justify-content:space-between;gap:8px;align-items:center;margin:6px 0}
|
||||||
|
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}
|
||||||
|
.pill{display:inline-block;background:#0b2547;color:#b8d4ff;border-radius:999px;padding:2px 8px;margin:2px 6px 0 0;font-size:12px}
|
||||||
|
.bar{height:10px;background:#091428;border-radius:999px;overflow:hidden}
|
||||||
|
.fill{height:100%;background:var(--accent);width:0%}
|
||||||
|
footer{margin-top:18px;color:var(--muted);font-size:12px}
|
||||||
|
.disks table{width:100%;border-collapse:collapse}
|
||||||
|
.disks th,.disks td{padding:8px;border-bottom:1px solid #1b2a4a;text-align:left;font-size:14px}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Windows Systeminfo</h1>
|
||||||
|
<h2><div class="v mono" id="hostname2"></div></h2>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="k">Host</div>
|
||||||
|
<div class="row"><div>Hostname</div><div class="v mono" id="hostname"></div></div>
|
||||||
|
<div class="row"><div>User</div><div class="v mono" id="username"></div></div>
|
||||||
|
<div class="row"><div>Uptime</div><div class="v" id="uptime"></div></div>
|
||||||
|
<div class="row"><div>Boot</div><div class="v" id="boottime"></div></div>
|
||||||
|
<div class="row"><div>Arch</div><div class="v mono" id="arch"></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="k">OS</div>
|
||||||
|
<div class="row"><div>Betriebssystem</div><div class="v" id="os"></div></div>
|
||||||
|
<div class="row"><div>Platform</div><div class="v" id="platform"></div></div>
|
||||||
|
<div class="row"><div>Kernel</div><div class="v mono" id="kernel"></div></div>
|
||||||
|
<div class="row"><div>Stand</div><div class="v" id="ts"></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="k">CPU</div>
|
||||||
|
<div class="row"><div>Modell</div><div class="v" id="cpu_model"></div></div>
|
||||||
|
<div class="row"><div>Kerne (phys/log)</div><div class="v" id="cores"></div></div>
|
||||||
|
<div class="row"><div>Auslastung</div><div class="v" id="cpu_pct"></div></div>
|
||||||
|
<div class="bar"><div class="fill" id="cpu_bar"></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="k">Speicher</div>
|
||||||
|
<div class="row"><div>RAM</div><div class="v" id="ram"></div></div>
|
||||||
|
<div class="bar"><div class="fill" id="ram_bar"></div></div>
|
||||||
|
<div class="row" style="margin-top:8px;"><div>Swap</div><div class="v" id="swap"></div></div>
|
||||||
|
<div class="bar"><div class="fill" id="swap_bar"></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="k">Netzwerk</div>
|
||||||
|
<div id="ifaces"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card disks">
|
||||||
|
<div class="k">Laufwerke</div>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Mount</th><th>Typ</th><th>Belegt</th><th>Gesamt</th><th>%</th></tr></thead>
|
||||||
|
<tbody id="disks"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer>Aktualisiert alle 2s</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function bytes(n){ if(n===0||n===undefined) return "0 B"; const u=['B','KB','MB','GB','TB','PB']; let i=Math.floor(Math.log(n)/Math.log(1024)); i=Math.min(i,u.length-1); return (n/Math.pow(1024,i)).toFixed(1)+" "+u[i]; }
|
||||||
|
function pct(p){ return (p||0).toFixed(1)+"%"; }
|
||||||
|
function hms(sec){ sec = Math.floor(sec||0); const d=Math.floor(sec/86400); sec%=86400; const h=Math.floor(sec/3600); sec%=3600; const m=Math.floor(sec/60); const s=sec%60; let out=[]; if(d) out.push(d+"d"); if(h) out.push(h+"h"); if(m) out.push(m+"m"); out.push(s+"s"); return out.join(" "); }
|
||||||
|
|
||||||
|
async function load(){
|
||||||
|
try{
|
||||||
|
const r = await fetch('/api/summary');
|
||||||
|
const j = await r.json();
|
||||||
|
|
||||||
|
document.getElementById('hostname').textContent = j.hostname || '-';
|
||||||
|
document.getElementById('hostname2').textContent = j.hostname || '-';
|
||||||
|
document.getElementById('username').textContent = j.username || '-';
|
||||||
|
document.getElementById('uptime').textContent = hms(j.uptime_seconds);
|
||||||
|
document.getElementById('boottime').textContent = j.boot_time ? new Date(j.boot_time).toLocaleString() : '-';
|
||||||
|
document.getElementById('arch').textContent = j.arch || '-';
|
||||||
|
|
||||||
|
document.getElementById('os').textContent = (j.os||'') ;
|
||||||
|
document.getElementById('platform').textContent = (j.platform||'') + (j.platform_version?(" "+j.platform_version):"");
|
||||||
|
document.getElementById('kernel').textContent = j.kernel_version || '-';
|
||||||
|
document.getElementById('ts').textContent = j.timestamp ? new Date(j.timestamp).toLocaleString() : '-';
|
||||||
|
|
||||||
|
document.getElementById('cpu_model').textContent= j.cpu_model || '-';
|
||||||
|
document.getElementById('cores').textContent = (j.physical_cores||0) + " / " + (j.logical_cores||0);
|
||||||
|
document.getElementById('cpu_pct').textContent = pct(j.cpu_percent);
|
||||||
|
document.getElementById('cpu_bar').style.width = (j.cpu_percent||0)+"%";
|
||||||
|
|
||||||
|
document.getElementById('ram').textContent = bytes(j.memory_used)+" / "+bytes(j.memory_total)+" ("+pct(j.memory_used_percent)+")";
|
||||||
|
document.getElementById('ram_bar').style.width = (j.memory_used_percent||0)+"%";
|
||||||
|
|
||||||
|
document.getElementById('swap').textContent = bytes(j.swap_used)+" / "+bytes(j.swap_total)+" ("+pct(j.swap_used_percent)+")";
|
||||||
|
document.getElementById('swap_bar').style.width = (j.swap_used_percent||0)+"%";
|
||||||
|
|
||||||
|
const wrap = document.getElementById('ifaces'); wrap.innerHTML='';
|
||||||
|
(j.interfaces||[]).forEach(n=>{
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.innerHTML = '<div class="row"><div>'+n.name+'</div><div class="mono">'+(n.mac||'-')+'</div></div>';
|
||||||
|
(n.addresses||[]).forEach(ip=>{ const span=document.createElement('span'); span.className='pill mono'; span.textContent=ip; div.appendChild(span); });
|
||||||
|
wrap.appendChild(div);
|
||||||
|
});
|
||||||
|
|
||||||
|
const tbody = document.getElementById('disks'); tbody.innerHTML='';
|
||||||
|
(j.disks||[]).forEach(d=>{
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = '<td class="mono">'+d.mountpoint+'</td><td>'+d.fstype+'</td><td>'+bytes(d.used)+'</td><td>'+bytes(d.total)+'</td><td>'+pct(d.used_percent)+'</td>';
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
}catch(e){ console.error(e); }
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
setInterval(load, 2000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`))
|
||||||
|
|
||||||
|
func indexHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
if err := page.Execute(w, nil); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sum := collectSummary()
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
if err := enc.Encode(sum); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
startCPUSampler()
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/", indexHandler)
|
||||||
|
mux.HandleFunc("/api/summary", apiHandler)
|
||||||
|
|
||||||
|
addr := "127.0.0.1:8080" // bind strictly to loopback (IPv4)
|
||||||
|
log.Printf("Starte lokales Dashboard auf http://%s … (nur von diesem Rechner erreichbar)", addr)
|
||||||
|
if err := http.ListenAndServe(addr, mux); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user