4 Commits

Author SHA1 Message Date
f4ed36121e Update für Device GUID
All checks were successful
build-binaries / build (.exe, amd64, windows) (push) Successful in 10m24s
build-binaries / release (push) Successful in 14s
build-binaries / publish-agent (push) Successful in 13s
2025-12-11 10:16:19 +01:00
de66b186b2 main.go aktualisiert
All checks were successful
build-binaries / build (.exe, amd64, windows) (push) Successful in 10m12s
build-binaries / release (push) Successful in 13s
build-binaries / publish-agent (push) Successful in 9s
2025-11-12 07:58:36 +00:00
59d8fb3e5d Anpassung auf Breite 1200
All checks were successful
build-binaries / build (.exe, amd64, windows) (push) Successful in 10m14s
build-binaries / release (push) Successful in 15s
build-binaries / publish-agent (push) Successful in 10s
2025-11-02 17:33:39 +01:00
a2cb8450e2 Template angepasst (Hell).
All checks were successful
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
2025-11-02 17:22:13 +01:00
2 changed files with 235 additions and 49 deletions

236
main.go
View File

@@ -81,11 +81,123 @@ type InstalledApp struct {
Source string `json:"source"` // HKLM-64, HKLM-32, HKCU Source string `json:"source"` // HKLM-64, HKLM-32, HKCU
} }
type DeviceInfo struct {
InstanceID string `json:"instance_id"`
Class string `json:"class"`
ClassGUID string `json:"class_guid"`
FriendlyName string `json:"friendly_name"`
Manufacturer string `json:"manufacturer"`
Status string `json:"status"`
Present bool `json:"present"`
}
var ( var (
mu sync.RWMutex mu sync.RWMutex
lastCPUUsage float64 lastCPUUsage float64
) )
func getPnpDevices() ([]DeviceInfo, error) {
// PowerShell: aktuelle PnP-Geräte, inkl. ClassGuid
cmd := exec.Command(
"powershell",
"-NoProfile",
"-NonInteractive",
"-Command",
`Get-PnpDevice -PresentOnly | Select-Object InstanceId,Class,ClassGuid,FriendlyName,Manufacturer,Status,Present | ConvertTo-Json -Depth 3`,
)
out, err := cmd.Output()
if err != nil {
log.Printf("getPnpDevices: powershell error: %v", err)
return nil, err
}
if len(out) == 0 {
return []DeviceInfo{}, nil
}
// PS gibt entweder ein Objekt oder ein Array zurück → wie bei getWinNetProfiles behandeln
type psDevice struct {
InstanceId string `json:"InstanceId"`
Class string `json:"Class"`
ClassGuid string `json:"ClassGuid"`
FriendlyName string `json:"FriendlyName"`
Manufacturer string `json:"Manufacturer"`
Status string `json:"Status"`
Present interface{} `json:"Present"`
}
parsePresent := func(v interface{}) bool {
switch t := v.(type) {
case bool:
return t
case string:
// "True"/"False" etc.
return strings.EqualFold(t, "true")
default:
return false
}
}
var arr []psDevice
if err := json.Unmarshal(out, &arr); err == nil {
res := make([]DeviceInfo, 0, len(arr))
for _, d := range arr {
res = append(res, DeviceInfo{
InstanceID: d.InstanceId,
Class: d.Class,
ClassGUID: d.ClassGuid,
FriendlyName: d.FriendlyName,
Manufacturer: d.Manufacturer,
Status: d.Status,
Present: parsePresent(d.Present),
})
}
return res, nil
}
// Ein einzelnes Objekt
var single psDevice
if err := json.Unmarshal(out, &single); err != nil {
log.Printf("getPnpDevices: cannot unmarshal: %v -- out: %s", err, string(out))
return nil, err
}
return []DeviceInfo{
{
InstanceID: single.InstanceId,
Class: single.Class,
ClassGUID: single.ClassGuid,
FriendlyName: single.FriendlyName,
Manufacturer: single.Manufacturer,
Status: single.Status,
Present: parsePresent(single.Present),
},
}, nil
}
func devicesHandler(w http.ResponseWriter, r *http.Request) {
devs, err := getPnpDevices()
if err != nil {
http.Error(w, "failed to enumerate devices: "+err.Error(), http.StatusInternalServerError)
return
}
// Optional: Filter nur USB-Geräte (InstanceID beginnt mit "USB\")
if r.URL.Query().Get("only_usb") == "1" {
filtered := make([]DeviceInfo, 0, len(devs))
for _, d := range devs {
if strings.HasPrefix(strings.ToUpper(d.InstanceID), "USB\\") {
filtered = append(filtered, d)
}
}
devs = filtered
}
w.Header().Set("Content-Type", "application/json")
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
_ = enc.Encode(devs)
}
func readUninstallKey(base registry.Key, path, src string) []InstalledApp { func readUninstallKey(base registry.Key, path, src string) []InstalledApp {
k, err := registry.OpenKey(base, path, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE) k, err := registry.OpenKey(base, path, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
if err != nil { if err != nil {
@@ -359,25 +471,99 @@ var page = template.Must(template.New("index").Parse(`<!doctype html>
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>Windows Systeminfo</title> <title>Windows Systeminfo</title>
<style> <style>
:root { --bg:#000000; --fg:#e7eefc; --muted:#a3b1cc; --card:#111a2e; --accent:#5aa9ff; } :root {
--bg: #f5f6f8;
--fg: #121826;
--muted: #5d6b82;
--card: #ffffff;
--accent: #0066ff;
--border: rgba(12, 21, 37, 0.08);
}
*{box-sizing:border-box} *{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)} body{
h1{font-size:22px;margin:0 0 16px 0} max-width:1200px;
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 6px 0}
h2{margin:0 0 16px 0}
.grid{display:flex;flex-direction:column;gap:12px} .grid{display:flex;flex-direction:column;gap:12px}
.card{width:100%} .card{
.k{font-size:16px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em} width:100%;
background:var(--card);
border:1px solid var(--border);
border-radius:16px;
padding:14px 16px 10px 16px;
box-shadow:0 10px 30px rgba(15,23,42,0.03);
}
.k{font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:4px}
.v{font-weight:600} .v{font-weight:600}
.row{display:flex;justify-content:space-between;gap:8px;align-items:center;margin:6px 0} .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} .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}
.stereo{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color: #ffd500ff} .stereo{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color: #ff0000ff}
.pill{display:inline-block;background: #0b2547;color: #00ff2aff;border-radius:999px;padding:2px 8px;margin:2px 6px 0 0;font-size:14px} .pill{
.pill2{display:inline-block;background: #ffe100ff;color: #282828ff;border-radius:999px;padding:2px 8px;margin:2px 6px 0 0;font-size:14px} display:inline-block;
.bar{height:10px;background:#091428;border-radius:999px;overflow:hidden} background:#e8f1ff;
.fill{height:100%;background:var(--accent);width:0%} color:#0047cc;
border-radius:999px;
padding:2px 8px;
margin:2px 6px 0 0;
font-size:14px;
}
.pill2{
display:inline-block;
background:#ffe480;
color:#3e3e3e;
border-radius:999px;
padding:2px 8px;
margin:2px 6px 0 0;
font-size:14px;
}
.bar{
height:10px;
background:#edf0f5;
border-radius:999px;
overflow:hidden;
}
.fill{
height:100%;
background:var(--accent);
width:0%;
transition:width .3s ease-out;
}
footer{margin-top:18px;color:var(--muted);font-size:12px} footer{margin-top:18px;color:var(--muted);font-size:12px}
.disks table{width:100%;border-collapse:collapse} .disks table{width:100%;border-collapse:collapse}
.disks th,.disks td{padding:8px;border-bottom:1px solid #1b2a4a;text-align:left;font-size:14px} .disks th,.disks td{
padding:8px;
border-bottom:1px solid #e5e9f2;
text-align:left;
font-size:14px
}
.disks th{background:#f5f6f8;font-weight:500}
button#btnLoadApps{
border:none;
background:#e8f1ff;
color:#0047cc;
font-weight:500;
}
input#appsFilter{
background:#ffffff;
color:var(--fg);
border:1px solid #d0d7e2;
border-radius:8px;
}
table thead th{font-size:13px;color:#3b4560}
@media (prefers-color-scheme: dark) {
/* optional: falls Host dunkles Theme erzwingt */
:root {
--bg:#f5f6f8;
--fg:#121826;
--card:#ffffff;
}
}
</style> </style>
</head> </head>
<body> <body>
@@ -434,17 +620,17 @@ var page = template.Must(template.New("index").Parse(`<!doctype html>
<div class="row" style="gap:8px;align-items:center;"> <div class="row" style="gap:8px;align-items:center;">
<button id="btnLoadApps" class="pill" style="cursor:pointer;">Laden</button> <button id="btnLoadApps" class="pill" style="cursor:pointer;">Laden</button>
<input id="appsFilter" type="text" placeholder="Suchen …" <input id="appsFilter" type="text" placeholder="Suchen …"
style="flex:1;padding:6px 10px;border-radius:8px;border:1px solid #1b2a4a;background:#0b1220;color:var(--fg)"> style="flex:1;padding:6px 10px;">
<span class="k" id="appsCount"></span> <span class="k" id="appsCount"></span>
</div> </div>
<div class="apps"> <div class="apps">
<table style="width:100%;border-collapse:collapse"> <table style="width:100%;border-collapse:collapse">
<thead> <thead>
<tr> <tr>
<th style="text-align:left;padding:8px;border-bottom:1px solid #1b2a4a">Name</th> <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Name</th>
<th style="text-align:left;padding:8px;border-bottom:1px solid #1b2a4a">Version</th> <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Version</th>
<th style="text-align:left;padding:8px;border-bottom:1px solid #1b2a4a">Publisher</th> <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Publisher</th>
<th style="text-align:left;padding:8px;border-bottom:1px solid #1b2a4a">Quelle</th> <th style="text-align:left;padding:8px;border-bottom:1px solid #e5e9f2">Quelle</th>
</tr> </tr>
</thead> </thead>
<tbody id="apps"></tbody> <tbody id="apps"></tbody>
@@ -499,7 +685,6 @@ async function load(){
div.appendChild(p); div.appendChild(p);
} }
(n.addresses||[]).forEach(ip=>{ const span=document.createElement('span'); span.className='pill mono'; span.textContent=ip; div.appendChild(span); }); (n.addresses||[]).forEach(ip=>{ const span=document.createElement('span'); span.className='pill mono'; span.textContent=ip; div.appendChild(span); });
wrap.appendChild(div); wrap.appendChild(div);
}); });
@@ -523,10 +708,10 @@ function renderApps(list){
(list||[]).forEach(a=>{ (list||[]).forEach(a=>{
const tr = document.createElement('tr'); const tr = document.createElement('tr');
tr.innerHTML = tr.innerHTML =
'<td class="mono" style="padding:6px 8px;border-bottom:1px solid #1b2a4a">'+(a.name||'-')+'</td>'+ '<td class="mono" style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.name||'-')+'</td>'+
'<td style="padding:6px 8px;border-bottom:1px solid #1b2a4a">'+(a.version||'')+'</td>'+ '<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.version||'')+'</td>'+
'<td style="padding:6px 8px;border-bottom:1px solid #1b2a4a">'+(a.publisher||'')+'</td>'+ '<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.publisher||'')+'</td>'+
'<td style="padding:6px 8px;border-bottom:1px solid #1b2a4a">'+(a.source||'')+'</td>'; '<td style="padding:6px 8px;border-bottom:1px solid #e5e9f2">'+(a.source||'')+'</td>';
tbody.appendChild(tr); tbody.appendChild(tr);
}); });
const c = document.getElementById('appsCount'); const c = document.getElementById('appsCount');
@@ -534,7 +719,7 @@ function renderApps(list){
} }
async function loadAppsOnce(){ async function loadAppsOnce(){
if(appsCache) return; // schon geladen if(appsCache) return;
try{ try{
const r = await fetch('/api/apps'); const r = await fetch('/api/apps');
appsCache = await r.json(); appsCache = await r.json();
@@ -562,10 +747,10 @@ document.addEventListener('DOMContentLoaded', ()=>{
}); });
} }
}); });
</script> </script>
</body> </body>
</html>`)) </html>
`))
func indexHandler(w http.ResponseWriter, r *http.Request) { func indexHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8") w.Header().Set("Content-Type", "text/html; charset=utf-8")
@@ -594,6 +779,7 @@ func runHTTP(ctx context.Context) error {
mux.HandleFunc("/", indexHandler) mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/api/apps", appsHandler) mux.HandleFunc("/api/apps", appsHandler)
mux.HandleFunc("/api/summary", apiHandler) mux.HandleFunc("/api/summary", apiHandler)
mux.HandleFunc("/api/devices", devicesHandler)
addr := ":24000" addr := ":24000"

Binary file not shown.