From 4035ef849738a6fb5787012bea6ecb5da74f285b Mon Sep 17 00:00:00 2001 From: Bohdan Shulha Date: Sat, 5 Oct 2024 22:30:24 +0200 Subject: [PATCH] feat: #206 display usage graphs for nodes --- app/Http/Controllers/NodeController.php | 42 ++- package-lock.json | 115 ++++++- package.json | 4 +- resources/js/Components/Card.vue | 13 + resources/js/Pages/Nodes/Index.vue | 137 +++++++-- resources/js/Pages/Nodes/Settings.vue | 91 ++++++ resources/js/Pages/Nodes/Show.vue | 391 +++++++++++++++++++----- resources/js/Pages/Nodes/ShowLayout.vue | 69 ++++- resources/js/app.js | 2 + routes/web.php | 1 + tailwind.config.js | 37 +-- 11 files changed, 770 insertions(+), 132 deletions(-) create mode 100644 resources/js/Pages/Nodes/Settings.vue diff --git a/app/Http/Controllers/NodeController.php b/app/Http/Controllers/NodeController.php index 13ffdbc..27f1220 100644 --- a/app/Http/Controllers/NodeController.php +++ b/app/Http/Controllers/NodeController.php @@ -8,6 +8,7 @@ use App\Models\Node; use App\Models\NodeTaskGroupType; use App\Models\Service; +use App\Services\Metrics; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Inertia\Inertia; @@ -21,9 +22,23 @@ public function index() { $nodes = Node::all(); + $query = <<<'QUERY' + union( + alias(round(rate(ptah_node_cpu_user + ptah_node_cpu_system) / rate(ptah_node_cpu_total) * 100), "cpu_usage"), + alias(round(ptah_node_memory_used_bytes / ptah_node_memory_total_bytes * 100), "memory_usage"), + alias(round(ptah_node_disk_used_bytes{path="/"} / ptah_node_disk_total_bytes{path="/"} * 100), "disk_usage"), + round({__name__=~"ptah_node_load_avg_(1|5|15)m"}, 0.01), + ) + QUERY; + + $nodeIds = $nodes->pluck('id')->toArray(); + + $metrics = Metrics::getMetrics($query, $nodeIds); + return Inertia::render('Nodes/Index', [ 'nodes' => $nodes, 'nodesLimitReached' => auth()->user()->currentTeam->quotas()->nodes->quotaReached(), + 'metrics' => $metrics, ]); } @@ -58,10 +73,33 @@ public function store(StoreNodeRequest $request) return to_route('nodes.show', ['node' => $node->id]); } + public function show(Node $node) + { + $query = <<<'QUERY' + union( + alias(round(rate(ptah_node_cpu_user + ptah_node_cpu_system) / rate(ptah_node_cpu_total) * 100), "cpu_usage"), + alias(round(ptah_node_memory_used_bytes / ptah_node_memory_total_bytes * 100), "memory_usage"), + alias(round(ptah_node_swap_used_bytes / ptah_node_swap_total_bytes * 100), "swap_usage"), + alias(round(ptah_node_disk_used_bytes{path="/"} / ptah_node_disk_total_bytes{path="/"} * 100), "disk_usage"), + alias(round(rate(ptah_node_network_rx_bytes / 1024)), "network_rx_bytes"), + alias(round(rate(ptah_node_network_tx_bytes / 1024)), "network_tx_bytes"), + alias(round(increase(ptah_caddy_http_requests_count)), "http_requests_count"), + alias(sum(increase(ptah_caddy_http_requests_duration_bucket)) by (le), "http_requests_duration"), + ) + QUERY; + + $metrics = Metrics::getMetricsRange($query, [$node->id]); + + return Inertia::render('Nodes/Show', [ + 'node' => $node, + 'metrics' => $metrics, + ]); + } + /** * Display the specified resource. */ - public function show(Node $node) + public function settings(Node $node) { $initTaskGroup = $node->actualTaskGroup(NodeTaskGroupType::InitSwarm); if ($initTaskGroup?->is_completed) { @@ -89,7 +127,7 @@ public function show(Node $node) $registryTaskGroup = null; } - return Inertia::render('Nodes/Show', [ + return Inertia::render('Nodes/Settings', [ 'node' => $node, 'isLastNode' => $node->team->nodes->count() === 1, 'initTaskGroup' => $initTaskGroup ?: $joinTaskGroup, diff --git a/package-lock.json b/package-lock.json index e13af6a..8707ae5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,10 +5,12 @@ "packages": { "": { "dependencies": { + "apexcharts": "^3.54.0", "dayjs": "^1.11.11", "handlebars": "^4.7.8", "lodash.set": "^4.3.2", - "swrv": "^1.0.4" + "swrv": "^1.0.4", + "vue3-apexcharts": "^1.6.0" }, "devDependencies": { "@formkit/auto-animate": "^0.8.2", @@ -1048,6 +1050,11 @@ } } }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, "node_modules/ansi-escapes": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", @@ -1106,6 +1113,20 @@ "node": ">= 8" } }, + "node_modules/apexcharts": { + "version": "3.54.0", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.54.0.tgz", + "integrity": "sha512-ZgI/seScffjLpwNRX/gAhIkAhpCNWiTNsdICv7qxnF0xisI23XSsaENUKIcMlyP1rbe8ECgvybDnp7plZld89A==", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -3429,6 +3450,89 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/swrv": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz", @@ -3681,6 +3785,15 @@ "vue": "^3.0.0" } }, + "node_modules/vue3-apexcharts": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/vue3-apexcharts/-/vue3-apexcharts-1.6.0.tgz", + "integrity": "sha512-gemKFXpw4TuVcllwyKJGYjTwiJQxxCUwbXsiiEEZjs0zc9jvOHvreN8frXz7QbnYqMqOHF9D1TBqwENvoPNjLw==", + "peerDependencies": { + "apexcharts": "> 3.0.0", + "vue": "> 3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 12881d6..ef68ccf 100644 --- a/package.json +++ b/package.json @@ -27,10 +27,12 @@ "vue": "^3.3.13" }, "dependencies": { + "apexcharts": "^3.54.0", "dayjs": "^1.11.11", "handlebars": "^4.7.8", "lodash.set": "^4.3.2", - "swrv": "^1.0.4" + "swrv": "^1.0.4", + "vue3-apexcharts": "^1.6.0" }, "lint-staged": { "*.{js,vue,css,md,json}": "prettier --write", diff --git a/resources/js/Components/Card.vue b/resources/js/Components/Card.vue index e45932c..8fc08d2 100644 --- a/resources/js/Components/Card.vue +++ b/resources/js/Components/Card.vue @@ -1,5 +1,18 @@ + + diff --git a/resources/js/Pages/Nodes/Index.vue b/resources/js/Pages/Nodes/Index.vue index 5dc75b6..9d2cc8d 100644 --- a/resources/js/Pages/Nodes/Index.vue +++ b/resources/js/Pages/Nodes/Index.vue @@ -1,6 +1,6 @@ @@ -57,25 +104,77 @@ defineProps({ v-for="node in nodes" :key="node.id" :href="route('nodes.show', { node: node.id })" - class="bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg p-4 flex justify-around" + class="bg-white dark:bg-gray-800 overflow-hidden shadow sm:rounded-lg p-4 flex flex-col gap-4" > -
-
{{ node.name }}
- +
+
+
{{ node.name }}
+ +
+
+ +
-
- +
+
+
+
+ Load 1 / 5 / 15 min + {{ + avgLoad + }} +
+
+
+
+
+
+ CPU Usage + {{ + cpuUsage + }} +
+
+
+
+
+
+ Memory Usage + {{ + memoryUsage + }} +
+
+
+
+
+
+ Disk Usage + {{ + diskUsage + }} +
+
+
diff --git a/resources/js/Pages/Nodes/Settings.vue b/resources/js/Pages/Nodes/Settings.vue new file mode 100644 index 0000000..f3460ca --- /dev/null +++ b/resources/js/Pages/Nodes/Settings.vue @@ -0,0 +1,91 @@ + + + diff --git a/resources/js/Pages/Nodes/Show.vue b/resources/js/Pages/Nodes/Show.vue index f3460ca..bd0d184 100644 --- a/resources/js/Pages/Nodes/Show.vue +++ b/resources/js/Pages/Nodes/Show.vue @@ -1,91 +1,326 @@ +const props = defineProps(["node", "metrics"]); -