@axium/sysadmin 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/info.js +17 -5
- package/dist/common.d.ts +2 -2
- package/dist/info.d.ts +2 -2
- package/dist/info.js +1 -1
- package/locales/en.json +1 -0
- package/package.json +1 -1
- package/routes/sysadmin/systems/[id]/+page.svelte +46 -8
package/dist/client/info.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
1
2
|
import * as fs from 'node:fs';
|
|
2
3
|
import * as os from 'node:os';
|
|
3
4
|
/** Read a sysfs/procfs file, returning the trimmed contents or undefined if unreadable. */
|
|
@@ -140,15 +141,26 @@ function networkInterfaces() {
|
|
|
140
141
|
continue;
|
|
141
142
|
const operstate = read(`/sys/class/net/${name}/operstate`);
|
|
142
143
|
const connected = operstate === 'up' || read(`/sys/class/net/${name}/carrier`) === '1';
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const speed = connected && raw && Number(raw) > 0 ? BigInt(raw) : undefined;
|
|
146
|
-
// Wi-Fi devices expose a `wireless`/`phy80211` subdirectory.
|
|
144
|
+
const raw = Number(read(`/sys/class/net/${name}/speed`));
|
|
145
|
+
let speed = connected && Number.isSafeInteger(raw) && raw > 0 ? raw : undefined;
|
|
147
146
|
const wireless = fs.existsSync(`/sys/class/net/${name}/wireless`) || fs.existsSync(`/sys/class/net/${name}/phy80211`);
|
|
147
|
+
// Wi-Fi doesn't expose sysfs `speed`; the SSID and link bitrate come from `iw` instead.
|
|
148
|
+
let connection;
|
|
149
|
+
if (wireless && connected)
|
|
150
|
+
try {
|
|
151
|
+
const link = execFileSync('iw', ['dev', name, 'link'], { encoding: 'utf8' });
|
|
152
|
+
connection = /^\s*SSID:\s*(.+)$/m.exec(link)?.[1].trim() || undefined;
|
|
153
|
+
const bitrate = /^\s*tx bitrate:\s*([\d.]+)\s*MBit\/s/m.exec(link)?.[1];
|
|
154
|
+
if (bitrate)
|
|
155
|
+
speed = Math.round(Number(bitrate));
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
// that's okay
|
|
159
|
+
}
|
|
148
160
|
const uevent = read(`/sys/class/net/${name}/device/uevent`);
|
|
149
161
|
const id = uevent && /^PCI_ID=(.+)$/m.exec(uevent)?.[1];
|
|
150
162
|
const model = id ? pciName(id) : name;
|
|
151
|
-
result.push({ name, model, connected, wireless, speed });
|
|
163
|
+
result.push({ name, model, connected, wireless, connection, speed });
|
|
152
164
|
}
|
|
153
165
|
return result;
|
|
154
166
|
}
|
package/dist/common.d.ts
CHANGED
|
@@ -297,7 +297,7 @@ declare const SysadminClientToServer: {
|
|
|
297
297
|
connected: z.ZodBoolean;
|
|
298
298
|
wireless: z.ZodBoolean;
|
|
299
299
|
connection: z.ZodOptional<z.ZodString>;
|
|
300
|
-
speed: z.ZodOptional<z.
|
|
300
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
301
301
|
}, z.core.$strip>>;
|
|
302
302
|
arch: z.ZodString;
|
|
303
303
|
machine: z.ZodString;
|
|
@@ -346,7 +346,7 @@ declare const SysadminServerToClient: {
|
|
|
346
346
|
connected: z.ZodBoolean;
|
|
347
347
|
wireless: z.ZodBoolean;
|
|
348
348
|
connection: z.ZodOptional<z.ZodString>;
|
|
349
|
-
speed: z.ZodOptional<z.
|
|
349
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
350
350
|
}, z.core.$strip>>;
|
|
351
351
|
arch: z.ZodString;
|
|
352
352
|
machine: z.ZodString;
|
package/dist/info.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare const NetworkInterface: z.ZodObject<{
|
|
|
44
44
|
connected: z.ZodBoolean;
|
|
45
45
|
wireless: z.ZodBoolean;
|
|
46
46
|
connection: z.ZodOptional<z.ZodString>;
|
|
47
|
-
speed: z.ZodOptional<z.
|
|
47
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
48
48
|
}, z.core.$strip>;
|
|
49
49
|
export interface NetworkInterface extends z.infer<typeof NetworkInterface> {
|
|
50
50
|
}
|
|
@@ -80,7 +80,7 @@ export declare const SystemInfo: z.ZodObject<{
|
|
|
80
80
|
connected: z.ZodBoolean;
|
|
81
81
|
wireless: z.ZodBoolean;
|
|
82
82
|
connection: z.ZodOptional<z.ZodString>;
|
|
83
|
-
speed: z.ZodOptional<z.
|
|
83
|
+
speed: z.ZodOptional<z.ZodNumber>;
|
|
84
84
|
}, z.core.$strip>>;
|
|
85
85
|
arch: z.ZodString;
|
|
86
86
|
machine: z.ZodString;
|
package/dist/info.js
CHANGED
|
@@ -30,7 +30,7 @@ export const NetworkInterface = z.object({
|
|
|
30
30
|
connected: z.boolean(),
|
|
31
31
|
wireless: z.boolean(),
|
|
32
32
|
connection: z.string().optional(),
|
|
33
|
-
speed: z.
|
|
33
|
+
speed: z.number().nonnegative().optional(),
|
|
34
34
|
});
|
|
35
35
|
export const SystemInfo = z.object({
|
|
36
36
|
cpus: CPU.array(),
|
package/locales/en.json
CHANGED
package/package.json
CHANGED
|
@@ -23,6 +23,12 @@
|
|
|
23
23
|
return text('sysadmin.system.usage', { used: formatBytes(used), total: formatBytes(total) });
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function speedText(speed: number): string {
|
|
27
|
+
if (speed < 1000) return `${speed} MBit/s`;
|
|
28
|
+
const gb = speed / 1000;
|
|
29
|
+
return `${gb % 1 === 0 ? gb : gb.toFixed(1)} GBit/s`;
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
const socket = await connect();
|
|
27
33
|
|
|
28
34
|
async function loadInfo() {
|
|
@@ -139,18 +145,23 @@
|
|
|
139
145
|
<div class="component">
|
|
140
146
|
<h3><Icon i="network-wired" /> {text('sysadmin.system.network')}</h3>
|
|
141
147
|
{#each info.networkInterfaces as iface}
|
|
142
|
-
<div class="line">
|
|
143
|
-
<span class="icon-text">
|
|
148
|
+
<div class="net-line">
|
|
149
|
+
<span class="icon-text net-name">
|
|
144
150
|
<Icon i={iface.wireless ? 'wifi' : 'ethernet'} />
|
|
145
151
|
{iface.name}
|
|
146
152
|
</span>
|
|
147
|
-
<span class="subtle">{iface.model}</span>
|
|
148
|
-
<span class=
|
|
149
|
-
{iface.connected ?
|
|
153
|
+
<span class="subtle net-model">{iface.model}</span>
|
|
154
|
+
<span class="net-state">
|
|
155
|
+
<span class={['net-status', iface.connected ? 'online' : 'offline']}>
|
|
156
|
+
{iface.connected ? text('sysadmin.system.connected') : text('sysadmin.system.disconnected')}
|
|
157
|
+
{#if iface.connection}
|
|
158
|
+
<span class="subtle">{text('sysadmin.system.connected_to', { connection: iface.connection })}</span>
|
|
159
|
+
{/if}
|
|
160
|
+
</span>
|
|
161
|
+
{#if iface.speed}
|
|
162
|
+
<span class="subtle">{speedText(iface.speed)}</span>
|
|
163
|
+
{/if}
|
|
150
164
|
</span>
|
|
151
|
-
{#if iface.speed}
|
|
152
|
-
<span>{iface.speed} Mbit/s</span>
|
|
153
|
-
{/if}
|
|
154
165
|
</div>
|
|
155
166
|
{/each}
|
|
156
167
|
</div>
|
|
@@ -266,6 +277,29 @@
|
|
|
266
277
|
}
|
|
267
278
|
}
|
|
268
279
|
|
|
280
|
+
.net-line {
|
|
281
|
+
display: flex;
|
|
282
|
+
align-items: center;
|
|
283
|
+
gap: 0.5em 1em;
|
|
284
|
+
flex-wrap: wrap;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.net-name {
|
|
288
|
+
font-weight: bold;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* Push the status/speed cluster to the trailing edge; let the model take the slack. */
|
|
292
|
+
.net-model {
|
|
293
|
+
margin-right: auto;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.net-state {
|
|
297
|
+
display: inline-flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
gap: 0.35em 0.75em;
|
|
300
|
+
flex-wrap: wrap;
|
|
301
|
+
}
|
|
302
|
+
|
|
269
303
|
dl {
|
|
270
304
|
display: grid;
|
|
271
305
|
grid-template-columns: max-content 1fr;
|
|
@@ -286,5 +320,9 @@
|
|
|
286
320
|
padding: 1em;
|
|
287
321
|
padding-bottom: 5em;
|
|
288
322
|
}
|
|
323
|
+
|
|
324
|
+
.net-model {
|
|
325
|
+
flex-basis: 100%;
|
|
326
|
+
}
|
|
289
327
|
}
|
|
290
328
|
</style>
|