generated from sendnrw/template_golang
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4ed36121e | |||
| de66b186b2 | |||
| 59d8fb3e5d | |||
| a2cb8450e2 |
284
main.go
284
main.go
@@ -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>
|
||||||
@@ -429,28 +615,28 @@ var page = template.Must(template.New("index").Parse(`<!doctype html>
|
|||||||
<tbody id="disks"></tbody>
|
<tbody id="disks"></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="k">Installierte Programme</div>
|
<div class="k">Installierte Programme</div>
|
||||||
<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>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer>Aktualisiert alle 2s</footer>
|
<footer>Aktualisiert alle 2s</footer>
|
||||||
@@ -466,7 +652,7 @@ async function load(){
|
|||||||
const j = await r.json();
|
const j = await r.json();
|
||||||
|
|
||||||
document.getElementById('hostname').textContent = j.hostname || '-';
|
document.getElementById('hostname').textContent = j.hostname || '-';
|
||||||
document.getElementById('hostname2').textContent = j.hostname || '-';
|
document.getElementById('hostname2').textContent = j.hostname || '-';
|
||||||
document.getElementById('username').textContent = j.username || '-';
|
document.getElementById('username').textContent = j.username || '-';
|
||||||
document.getElementById('uptime').textContent = hms(j.uptime_seconds);
|
document.getElementById('uptime').textContent = hms(j.uptime_seconds);
|
||||||
document.getElementById('boottime').textContent = j.boot_time ? new Date(j.boot_time).toLocaleString() : '-';
|
document.getElementById('boottime').textContent = j.boot_time ? new Date(j.boot_time).toLocaleString() : '-';
|
||||||
@@ -492,14 +678,13 @@ async function load(){
|
|||||||
(j.interfaces||[]).forEach(n=>{
|
(j.interfaces||[]).forEach(n=>{
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.innerHTML = '<div class="row"><div>'+n.name+'</div><div class="mono">'+(n.mac||'-')+'</div></div>';
|
div.innerHTML = '<div class="row"><div>'+n.name+'</div><div class="mono">'+(n.mac||'-')+'</div></div>';
|
||||||
if (n.profile) {
|
if (n.profile) {
|
||||||
const p = document.createElement('span');
|
const p = document.createElement('span');
|
||||||
p.className = 'pill2';
|
p.className = 'pill2';
|
||||||
p.textContent = n.profile;
|
p.textContent = n.profile;
|
||||||
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"
|
||||||
|
|
||||||
|
|||||||
BIN
pcinfo.exe
BIN
pcinfo.exe
Binary file not shown.
Reference in New Issue
Block a user