android comp

This commit is contained in:
ciomek committed 2026-09-21 19:36:04 +02:00
1 parent 4ce43a9c41
commit 459188346e
4 files changed
+78 -16

No files matched your search

+19 -14
View File
@@ -15,17 +15,24 @@ class HardwareStatistics:
self.os_info = None self.os_info = None
self.boot_time = None self.boot_time = None
@staticmethod
def _try(read, fallback=None):
try:
return read()
except Exception:
return fallback
@staticmethod @staticmethod
def collect(): def collect():
self = HardwareStatistics() self = HardwareStatistics()
self.cpu = self._get_cpu_stats() self.cpu = self._try(self._get_cpu_stats)
self.mem = self._get_memory_stats() self.mem = self._try(self._get_memory_stats)
self.gpu = self._get_gpu_stats() self.gpu = self._try(self._get_gpu_stats, [])
self.disk = self._get_disk_stats() self.disk = self._try(self._get_disk_stats, [])
self.os_info = self._get_os_info() self.os_info = self._try(self._get_os_info)
self.boot_time = datetime.datetime.fromtimestamp( self.boot_time = self._try(
psutil.boot_time() lambda: datetime.datetime.fromtimestamp(psutil.boot_time()).isoformat()
).isoformat() )
return self return self
def to_dict(self): def to_dict(self):
@@ -63,7 +70,8 @@ class HardwareStatistics:
stats["gpu_mem_total_gb"] = round(sum(g["memory_total"] for g in self.gpu) / 1024, 2) stats["gpu_mem_total_gb"] = round(sum(g["memory_total"] for g in self.gpu) / 1024, 2)
stats["gpu_mem_used_gb"] = round(sum(g["memory_used"] for g in self.gpu) / 1024, 2) stats["gpu_mem_used_gb"] = round(sum(g["memory_used"] for g in self.gpu) / 1024, 2)
stats["uptime_hours"] = HardwareScorer.longevity_score(self) if self.boot_time:
stats["uptime_hours"] = HardwareScorer.longevity_score(self)
return stats return stats
def to_json(self): def to_json(self):
@@ -87,7 +95,7 @@ class HardwareStatistics:
return { return {
"cores": psutil.cpu_count(logical=False), "cores": psutil.cpu_count(logical=False),
"threads": psutil.cpu_count(logical=True), "threads": psutil.cpu_count(logical=True),
"usage_percent": psutil.cpu_percent(interval=0.2) "usage_percent": self._try(lambda: psutil.cpu_percent(interval=0.2))
} }
def _get_memory_stats(self): def _get_memory_stats(self):
@@ -100,10 +108,7 @@ class HardwareStatistics:
} }
def _get_gpu_stats(self): def _get_gpu_stats(self):
try: gpus = GPUtil.getGPUs()
gpus = GPUtil.getGPUs()
except Exception:
return []
if not gpus: if not gpus:
return [] return []
return [ return [
+22 -1
View File
@@ -10,6 +10,7 @@
const STORAGE_KEY = "colony.map.v1"; const STORAGE_KEY = "colony.map.v1";
const PAD = 48; const PAD = 48;
const FIT_MS = 260; const FIT_MS = 260;
const COMPACT_MAX_WIDTH = 600;
const stage = document.getElementById("stage"); const stage = document.getElementById("stage");
const svgRoot = document.getElementById("map"); const svgRoot = document.getElementById("map");
@@ -149,10 +150,29 @@
updateControls(); updateControls();
} }
function regionView(region) {
return C.viewFor({ x: region.x - 40, y: region.y - 40, w: region.w + 80, h: region.h + 80 }, size, 24);
}
function fitRegion(net) { function fitRegion(net) {
const region = map && map.regionAt.get(net); const region = map && map.regionAt.get(net);
if (!region) return; if (!region) return;
animateTo(C.viewFor({ x: region.x - 40, y: region.y - 40, w: region.w + 80, h: region.h + 80 }, size, 24), true); animateTo(regionView(region), true);
}
// On a phone, fitting the whole map shrinks it below the zoom where names and stats are drawn,
// so the first view is the region of this node instead. COMPACT_MAX_WIDTH matches the phone
// breakpoint in style.css. This only sets `view` and `autoFit`; the caller applies the view afterwards.
let startedCompact = false;
function startCompact() {
if (startedCompact) return;
startedCompact = true; // decided once, on the first map: later changes to the window or the topology must not move the view
if (size.w > COMPACT_MAX_WIDTH) return;
const self = model.nodes.find((node) => node.self);
const region = self && map.regionAt.get(self.net);
if (!region) return;
autoFit = false;
view = regionView(region);
} }
function resize() { function resize() {
@@ -482,6 +502,7 @@
drawNodes(); drawNodes();
drawSpokes(); drawSpokes();
drawPorts(); drawPorts();
startCompact();
if (autoFit) fitAll(false); if (autoFit) fitAll(false);
else setView(view, false); else setView(view, false);
drawTrace(); drawTrace();
@@ -4,6 +4,11 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Colony</title> <title>Colony</title>
<script>
/* The Android app asks for ?theme=dark: a WebView reports the theme of its host app, not the phone's setting. */
const theme = new URLSearchParams(location.search).get("theme");
if (theme === "dark" || theme === "light") document.documentElement.dataset.theme = theme;
</script>
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
</head> </head>
<body> <body>
+32 -1
View File
@@ -18,7 +18,8 @@
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root:not([data-theme="light"]) {
color-scheme: dark;
--bg: #12161c; --bg: #12161c;
--card: #1b2129; --card: #1b2129;
--text: #e6eaf0; --text: #e6eaf0;
@@ -38,6 +39,26 @@
} }
} }
:root[data-theme="dark"] {
color-scheme: dark;
--bg: #12161c;
--card: #1b2129;
--text: #e6eaf0;
--muted: #93a0b3;
--line: #313a47;
--down: #6b7686;
--track: #2a3340;
--panel: rgba(27, 33, 41, 0.92);
--warn: #ffb04a;
--crit: #ff6b6b;
--n0: #6c9cff;
--n1: #3cc7c7;
--n2: #ab8cff;
--n3: #ff7cc8;
--n4: #e0b04a;
--n5: #8fd06a;
}
* { box-sizing: border-box; } * { box-sizing: border-box; }
html, body { height: 100%; } html, body { height: 100%; }
@@ -196,3 +217,13 @@ p { margin: 4px 0; }
.swatch.none { background: transparent; border: 2px dashed var(--muted); } .swatch.none { background: transparent; border: 2px dashed var(--muted); }
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; } .sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
/* Phones: the desktop sizes are small on a 400px screen, so the page text and touch targets grow. */
@media (max-width: 600px) {
body { font-size: 16px; }
header { padding: 0 12px 6px; }
h1 { font-size: 24px; }
.alert, .route, .muted, .legend { font-size: 14px; }
.legend { max-width: calc(100% - 24px); }
.legend, .legend-open { bottom: 64px; }
}