Skip to content

feat(frontend): metrics-sdk support and devbox implement#6613

Merged
zjy365 merged 18 commits intolabring:mainfrom
mlhiter:feat/metrics
Feb 6, 2026
Merged

feat(frontend): metrics-sdk support and devbox implement#6613
zjy365 merged 18 commits intolabring:mainfrom
mlhiter:feat/metrics

Conversation

@mlhiter
Copy link
Copy Markdown
Member

@mlhiter mlhiter commented Jan 27, 2026

No description provided.

@mlhiter mlhiter requested a review from a team as a code owner January 27, 2026 07:56
Copilot AI review requested due to automatic review settings January 27, 2026 07:56
@pull-request-size
Copy link
Copy Markdown

Whoa! Easy there, Partner!

This PR is too big. Please break it up into smaller PRs.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new TypeScript SDK package (sealos-metrics-sdk) for querying Prometheus/Victoria Metrics directly from Next.js applications. The SDK provides built-in Kubernetes authentication and type-safe interfaces for querying metrics across launchpad applications, databases, and MinIO storage.

Changes:

  • New metrics-sdk package with TypeScript client for Prometheus/Victoria Metrics queries
  • Authentication service using @kubernetes/client-node for K8s permission validation
  • Service modules for launchpad, database, MinIO, and raw metric queries with PromQL builders
  • Rollup build configuration for dual-format output (ESM and CJS)
  • Comprehensive documentation and test utilities

Reviewed changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 20 comments.

Show a summary per file
File Description
frontend/pnpm-lock.yaml Added dependencies for new metrics-sdk package including axios, dayjs, rollup plugins, and esbuild 0.27.2
frontend/packages/metrics-sdk/package.json Package configuration with dual exports (ESM/CJS), peer dependency on @kubernetes/client-node
frontend/packages/metrics-sdk/tsconfig.json TypeScript configuration with ES2017 target and strict mode enabled
frontend/packages/metrics-sdk/rollup.config.js Build configuration for bundling TypeScript with dual output formats
frontend/packages/metrics-sdk/src/client.ts Main SDK client exposing launchpad, database, minio, and raw query services
frontend/packages/metrics-sdk/src/auth/auth.ts Kubernetes authentication service with kubeconfig parsing and permission validation
frontend/packages/metrics-sdk/src/services/*.ts Service implementations for different metric types with PromQL query builders
frontend/packages/metrics-sdk/src/types/*.ts TypeScript type definitions for query parameters and responses
frontend/packages/metrics-sdk/src/constants/promql.ts PromQL query templates for various metric types and databases
frontend/packages/metrics-sdk/src/utils/time.ts Utility functions for timestamp formatting and conversion
frontend/packages/metrics-sdk/test/index.ts Manual test script for SDK validation
frontend/packages/metrics-sdk/README.md Comprehensive documentation with usage examples and API reference
Files not reviewed (1)
  • frontend/pnpm-lock.yaml: Language not supported

Comment on lines +75 to +93
console.log('🔍 Debug Info:');
console.log(' Query:', query);
console.log(' Endpoint:', this.baseURL + endpoint);
console.log(' Params:', formData);

const body = new URLSearchParams(
Object.entries(formData).reduce<Record<string, string>>((acc, [key, value]) => {
acc[key] = String(value);
return acc;
}, {})
).toString();
const response = await this.client.post(endpoint, body);

console.log(' Response status:', response.data.status);
console.log(' Result count:', response.data.data?.result?.length || 0);
if (response.data.data?.result?.length === 0) {
console.log(' ⚠️ Empty result!');
}
console.log('');
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug logging statements should be removed before production. These console.log calls will execute on every query and expose sensitive information like query parameters and results. Consider using a configurable logger or removing these debug statements entirely.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +30
```

**Replaces:**

Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README states the SDK "Replaces" three Go services (launchpad-monitor, database-monitor, minio-monitor), but this is misleading in a PR description. This new SDK provides an alternative client-side implementation but does not actually remove those services. Consider rephrasing to "Provides alternative to" or "Can replace" to avoid confusion.

Suggested change
```
**Replaces:**
```
**Can replace / provides alternative to:**

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +15
console.error('❌ KUBECONFIG environment variable not set');
process.exit(1);
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling issue: When kubeconfig is empty, the code continues execution and will fail later with less clear error messages. It would be better to throw an error immediately with a descriptive message rather than exiting the process, allowing the calling code to handle the error appropriately.

Suggested change
console.error('❌ KUBECONFIG environment variable not set');
process.exit(1);
throw new Error('❌ KUBECONFIG environment variable not set');

Copilot uses AI. Check for mistakes.

export interface MinioQueryParams extends BaseQueryParams {
query: MinioMetric;
type: 'minio';
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API design inconsistency: The MinioQueryParams interface requires both 'query' (typed as MinioMetric enum) and 'type' (hardcoded as literal 'minio'). The 'type' field seems redundant since the service is already minio-specific. This differs from other services where 'type' has semantic meaning. Consider removing the 'type' field or making it optional.

Suggested change
type: 'minio';
type?: 'minio';

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +14
constructor(kubeconfig: string) {
this.kubeconfig = kubeconfig;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security: The kubeconfig string is stored in memory and passed around. If this contains sensitive tokens or certificates, it could be exposed through error messages, logs, or memory dumps. Consider using more secure credential handling patterns, such as extracting only the necessary auth tokens and discarding the full kubeconfig after initial parsing.

Copilot uses AI. Check for mistakes.

export const toSeconds = (value: number | string): number => {
const num = typeof value === 'string' ? Number(value) : value;
return Math.abs(num) > 1e12 ? Math.floor(num / 1000) : Math.floor(num);
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent timestamp handling: The toSeconds function checks if the absolute value is greater than 1e12 to determine if it's in milliseconds, but this approach could fail for timestamps far in the future or past. A more reliable approach would be to check the actual magnitude or require explicit units. Additionally, using Math.abs could cause issues with negative timestamps (before epoch).

Suggested change
return Math.abs(num) > 1e12 ? Math.floor(num / 1000) : Math.floor(num);
if (!Number.isFinite(num)) {
return NaN;
}
// Infer unit based on proximity to current time in seconds vs milliseconds.
// This avoids misclassifying far-future timestamps purely by magnitude.
const nowMs = Date.now();
const nowSec = Math.floor(nowMs / 1000);
const tenYearsInSeconds = 10 * 365 * 24 * 60 * 60;
const tenYearsInMs = tenYearsInSeconds * 1000;
// If it's close to the current seconds-based timestamp, treat as seconds.
if (Math.abs(num - nowSec) <= tenYearsInSeconds) {
return Math.floor(num);
}
// If it's close to the current milliseconds-based timestamp, treat as milliseconds.
if (Math.abs(num - nowMs) <= tenYearsInMs) {
return Math.floor(num / 1000);
}
// Fallback: treat as seconds to avoid misclassifying extreme values as milliseconds.
return Math.floor(num);

Copilot uses AI. Check for mistakes.
export class MinioService extends BaseMetricsService {
private minioInstance: string;

constructor(baseURL: string, authService: any, minioInstance?: string) {
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'any' type annotation reduces type safety. Consider using a more specific type for the authService parameter. Based on the BaseMetricsService constructor, this should be typed as 'AuthService' instead of 'any'.

Suggested change
constructor(baseURL: string, authService: any, minioInstance?: string) {
constructor(
baseURL: string,
authService: ConstructorParameters<typeof BaseMetricsService>[1],
minioInstance?: string
) {

Copilot uses AI. Check for mistakes.

constructor(baseURL: string, authService: any, minioInstance?: string) {
super(baseURL, authService);
this.minioInstance = minioInstance || process.env.OBJECT_STORAGE_INSTANCE || '';
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for environment variable access. If OBJECT_STORAGE_INSTANCE is not set and minioInstance is not provided, this will default to an empty string which could lead to incorrect query construction. Consider throwing an error or documenting this behavior clearly.

Suggested change
this.minioInstance = minioInstance || process.env.OBJECT_STORAGE_INSTANCE || '';
const resolvedInstance = minioInstance ?? process.env.OBJECT_STORAGE_INSTANCE;
if (!resolvedInstance) {
throw new Error(
'MinIO instance is not configured. Provide a minioInstance argument or set the OBJECT_STORAGE_INSTANCE environment variable.'
);
}
this.minioInstance = resolvedInstance;

Copilot uses AI. Check for mistakes.
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: The test file contains hardcoded credential expectations via environment variables. While using environment variables is better than hardcoding values, the test file should include clear warnings about not committing .env files with actual credentials. Consider adding a .env.example file and documenting this in comments.

Suggested change
const __dirname = dirname(fileURLToPath(import.meta.url));
const __dirname = dirname(fileURLToPath(import.meta.url));
// NOTE: This test expects configuration via environment variables loaded from a local `.env` file.
// - Do NOT commit any `.env` files containing real credentials or kubeconfig information.
// - Instead, add a `.env.example` file (with non-sensitive placeholder values) to version control
// and copy it to `.env` locally when running this test.
// - Ensure `.env` is listed in `.gitignore` (or equivalent ignore configuration).

Copilot uses AI. Check for mistakes.
Comment on lines +1 to +11
export const LAUNCHPAD_QUERIES = {
cpu: 'round(sum(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=~"$namespace",pod=~"$pod.*"}) by (pod) / sum(cluster:namespace:pod_cpu:active:kube_pod_container_resource_limits{namespace=~"$namespace",pod=~"$pod.*"}) by (pod) * 100,0.01)',
memory:
'round(sum(container_memory_working_set_bytes{job="kubelet", metrics_path="/metrics/cadvisor",namespace=~"$namespace",pod=~"$pod.*"}) by(pod) / sum(cluster:namespace:pod_memory:active:kube_pod_container_resource_limits{namespace=~"$namespace",pod=~"$pod.*"}) by (pod)* 100, 0.01)',
average_cpu:
'avg(round(sum(node_namespace_pod_container:container_cpu_usage_seconds_total:sum_irate{namespace=~"$namespace",pod=~"$pod.*"}) by (pod) / sum(cluster:namespace:pod_cpu:active:kube_pod_container_resource_limits{namespace=~"$namespace",pod=~"$pod.*"}) by (pod) * 100,0.01))',
average_memory:
'avg(round(sum(container_memory_working_set_bytes{job="kubelet", metrics_path="/metrics/cadvisor",namespace=~"$namespace",pod=~"$pod.*",container!=""}) by(pod) / sum(cluster:namespace:pod_memory:active:kube_pod_container_resource_limits{namespace=~"$namespace",pod=~"$pod.*"}) by (pod) * 100, 0.01))',
storage:
'round((max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_used_bytes {namespace=~"$namespace", persistentvolumeclaim=~"@persistentvolumeclaim"})) / (max by (persistentvolumeclaim,namespace) (kubelet_volume_stats_capacity_bytes {namespace=~"$namespace", persistentvolumeclaim=~"@persistentvolumeclaim"})) * 100, 0.01)'
} as const;
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type safety issue: The type assertion 'as const' on line 11 creates a readonly tuple type, but the actual LAUNCHPAD_QUERIES object uses string keys like 'cpu', 'memory', etc. This mismatch means the LaunchpadMetric enum values must exactly match these keys. Consider adding a type constraint to ensure the enum and query keys stay in sync, or document this requirement clearly.

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov bot commented Jan 28, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.86%. Comparing base (c7661b4) to head (47fb04d).
⚠️ Report is 39 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6613   +/-   ##
=======================================
  Coverage   61.86%   61.86%           
=======================================
  Files           8        8           
  Lines         653      653           
=======================================
  Hits          404      404           
  Misses        202      202           
  Partials       47       47           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mlhiter mlhiter changed the title feat(frontend): metrics-sdk support feat(frontend): metrics-sdk support and devbox implement Jan 29, 2026
@zjy365 zjy365 added this to the v5.2 milestone Feb 6, 2026
@zjy365 zjy365 merged commit d0ba22f into labring:main Feb 6, 2026
57 checks passed
zzjin pushed a commit to zzjin/sealos that referenced this pull request Mar 1, 2026
* feat: basic feat

* feat: test

* feat: perf code to full feat

* chore: update readme.md

* feat: optional namespace

* perf: podName logic adjust

* chore: Prometheus->Metrics

* chore: test adjust

* chore: test logResult

* chore: readme.zh-CN.md

* chore: remove minio type

* chore: adjust launchpad storage to disk

* chore: transform enum to type

* fix: try to fix ts bug

* feat: devbox metrics support

* feat: adjust other route

* fix: whitelistKubernetesHosts sdk transform bug

* feat: cache
mlhiter added a commit to mlhiter/sealos that referenced this pull request Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants