uplink-cli 0.1.2 → 0.1.4
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/cli/src/subcommands/menu.ts +97 -81
- package/package.json +1 -1
|
@@ -8,6 +8,11 @@ import { homedir } from "os";
|
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
10
10
|
|
|
11
|
+
// Check if running from project directory (smoke tests require local scripts)
|
|
12
|
+
function isInProjectDir(): boolean {
|
|
13
|
+
return existsSync("scripts/test-comprehensive.sh") && existsSync("package.json");
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
type MenuChoice = {
|
|
12
17
|
label: string;
|
|
13
18
|
action?: () => Promise<string>;
|
|
@@ -471,33 +476,36 @@ export const menuCommand = new Command("menu")
|
|
|
471
476
|
// Only show other menu items if authentication succeeded
|
|
472
477
|
|
|
473
478
|
if (isAdmin) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
{
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
health = "unreachable";
|
|
487
|
-
}
|
|
479
|
+
const systemStatusItems: MenuChoice[] = [
|
|
480
|
+
{
|
|
481
|
+
label: "View Status",
|
|
482
|
+
action: async () => {
|
|
483
|
+
let health = "unknown";
|
|
484
|
+
try {
|
|
485
|
+
const res = await fetch(`${apiBase}/health`);
|
|
486
|
+
const json = await res.json().catch(() => ({}));
|
|
487
|
+
health = json.status || res.statusText || "unknown";
|
|
488
|
+
} catch {
|
|
489
|
+
health = "unreachable";
|
|
490
|
+
}
|
|
488
491
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
},
|
|
492
|
+
const stats = await apiRequest("GET", "/v1/admin/stats");
|
|
493
|
+
return [
|
|
494
|
+
`API health: ${health}`,
|
|
495
|
+
"Tunnels:",
|
|
496
|
+
` Active ${stats.tunnels.active} | Inactive ${stats.tunnels.inactive} | Deleted ${stats.tunnels.deleted} | Total ${stats.tunnels.total}`,
|
|
497
|
+
` Created last 24h: ${stats.tunnels.createdLast24h}`,
|
|
498
|
+
"Databases:",
|
|
499
|
+
` Ready ${stats.databases.ready} | Provisioning ${stats.databases.provisioning} | Failed ${stats.databases.failed} | Deleted ${stats.databases.deleted} | Total ${stats.databases.total}`,
|
|
500
|
+
` Created last 24h: ${stats.databases.createdLast24h}`,
|
|
501
|
+
].join("\n");
|
|
500
502
|
},
|
|
503
|
+
},
|
|
504
|
+
];
|
|
505
|
+
|
|
506
|
+
// Smoke tests only available when running from project directory
|
|
507
|
+
if (isInProjectDir()) {
|
|
508
|
+
systemStatusItems.push(
|
|
501
509
|
{
|
|
502
510
|
label: "Test: Tunnel",
|
|
503
511
|
action: async () => {
|
|
@@ -525,72 +533,80 @@ export const menuCommand = new Command("menu")
|
|
|
525
533
|
await runSmoke("test:comprehensive");
|
|
526
534
|
return "test:comprehensive completed";
|
|
527
535
|
},
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
action: async () => {
|
|
532
|
-
try {
|
|
533
|
-
const data = await apiRequest("GET", "/v1/admin/relay-status") as {
|
|
534
|
-
connectedTunnels?: number;
|
|
535
|
-
tunnels?: Array<{ token: string; clientIp: string; targetPort: number; connectedAt: string; connectedFor: string }>;
|
|
536
|
-
timestamp?: string;
|
|
537
|
-
error?: string;
|
|
538
|
-
message?: string;
|
|
539
|
-
};
|
|
540
|
-
|
|
541
|
-
if (data.error) {
|
|
542
|
-
return `Error: ${data.error}${data.message ? ` - ${data.message}` : ""}`;
|
|
543
|
-
}
|
|
544
|
-
if (!data.tunnels || data.tunnels.length === 0) {
|
|
545
|
-
return "No tunnels currently connected to the relay.";
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const lines = data.tunnels.map((t) =>
|
|
549
|
-
`${truncate(t.token, 12).padEnd(14)} ${t.clientIp.padEnd(16)} ${String(t.targetPort).padEnd(6)} ${t.connectedFor.padEnd(10)} ${truncate(t.connectedAt, 19)}`
|
|
550
|
-
);
|
|
536
|
+
}
|
|
537
|
+
);
|
|
538
|
+
}
|
|
551
539
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
label: "View Traffic Stats",
|
|
566
|
-
action: async () => {
|
|
567
|
-
const data = await apiRequest("GET", "/v1/admin/traffic-stats?sync=true") as {
|
|
568
|
-
count?: number;
|
|
569
|
-
aliases?: Array<{ alias: string; requests: number; bytesIn: number; bytesOut: number; lastSeenAt: string | null; lastStatus: number | null }>;
|
|
540
|
+
systemStatusItems.push(
|
|
541
|
+
{
|
|
542
|
+
label: "View Connected Tunnels",
|
|
543
|
+
action: async () => {
|
|
544
|
+
try {
|
|
545
|
+
const data = await apiRequest("GET", "/v1/admin/relay-status") as {
|
|
546
|
+
connectedTunnels?: number;
|
|
547
|
+
tunnels?: Array<{ token: string; clientIp: string; targetPort: number; connectedAt: string; connectedFor: string }>;
|
|
548
|
+
timestamp?: string;
|
|
549
|
+
error?: string;
|
|
550
|
+
message?: string;
|
|
570
551
|
};
|
|
571
552
|
|
|
572
|
-
|
|
573
|
-
|
|
553
|
+
if (data.error) {
|
|
554
|
+
return `Error: ${data.error}${data.message ? ` - ${data.message}` : ""}`;
|
|
555
|
+
}
|
|
556
|
+
if (!data.tunnels || data.tunnels.length === 0) {
|
|
557
|
+
return "No tunnels currently connected to the relay.";
|
|
558
|
+
}
|
|
574
559
|
|
|
575
|
-
const
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
return `${truncate(a.alias, 22).padEnd(24)} ${String(a.requests || 0).padStart(8)} ${formatBytes(a.bytesIn || 0).padStart(9)} ${formatBytes(a.bytesOut || 0).padStart(9)} ${String(a.lastStatus ?? "-").padStart(3)} ${last}`;
|
|
579
|
-
});
|
|
560
|
+
const lines = data.tunnels.map((t) =>
|
|
561
|
+
`${truncate(t.token, 12).padEnd(14)} ${t.clientIp.padEnd(16)} ${String(t.targetPort).padEnd(6)} ${t.connectedFor.padEnd(10)} ${truncate(t.connectedAt, 19)}`
|
|
562
|
+
);
|
|
580
563
|
|
|
581
564
|
return [
|
|
582
|
-
`
|
|
565
|
+
`Connected Tunnels: ${data.connectedTunnels}`,
|
|
583
566
|
"",
|
|
584
|
-
"
|
|
567
|
+
"Token Client IP Port Uptime Connected At",
|
|
585
568
|
"-".repeat(75),
|
|
586
569
|
...lines,
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
},
|
|
570
|
+
].join("\n");
|
|
571
|
+
} catch (err: any) {
|
|
572
|
+
return `Error: Failed to get relay status - ${err.message}`;
|
|
573
|
+
}
|
|
592
574
|
},
|
|
593
|
-
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
label: "View Traffic Stats",
|
|
578
|
+
action: async () => {
|
|
579
|
+
const data = await apiRequest("GET", "/v1/admin/traffic-stats?sync=true") as {
|
|
580
|
+
count?: number;
|
|
581
|
+
aliases?: Array<{ alias: string; requests: number; bytesIn: number; bytesOut: number; lastSeenAt: string | null; lastStatus: number | null }>;
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const aliases = data.aliases || [];
|
|
585
|
+
if (aliases.length === 0) return "No persisted alias stats yet.";
|
|
586
|
+
|
|
587
|
+
const top = aliases.slice(0, 25);
|
|
588
|
+
const lines = top.map((a) => {
|
|
589
|
+
const last = a.lastSeenAt ? truncate(a.lastSeenAt, 19) : "-";
|
|
590
|
+
return `${truncate(a.alias, 22).padEnd(24)} ${String(a.requests || 0).padStart(8)} ${formatBytes(a.bytesIn || 0).padStart(9)} ${formatBytes(a.bytesOut || 0).padStart(9)} ${String(a.lastStatus ?? "-").padStart(3)} ${last}`;
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
return [
|
|
594
|
+
`Aliases tracked: ${aliases.length}`,
|
|
595
|
+
"",
|
|
596
|
+
"Alias Requests In Out Sts Last Seen",
|
|
597
|
+
"-".repeat(75),
|
|
598
|
+
...lines,
|
|
599
|
+
aliases.length > top.length ? `\nShowing top ${top.length} by requests.` : "",
|
|
600
|
+
]
|
|
601
|
+
.filter(Boolean)
|
|
602
|
+
.join("\n");
|
|
603
|
+
},
|
|
604
|
+
}
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
mainMenu.push({
|
|
608
|
+
label: "System Status",
|
|
609
|
+
subMenu: systemStatusItems,
|
|
594
610
|
});
|
|
595
611
|
}
|
|
596
612
|
|
package/package.json
CHANGED