feat(platform): add /devops page with platform common devops package
- Add @bytelyst/devops backend endpoints to platform-service - Add /api/devops/version (public) and /api/devops/info (admin) endpoints - Add /devops page to admin-web using @bytelyst/devops/ui DevopsPanel - Add devops link to admin web sidebar navigation - Add build metadata and runtime information display - Follow trading web devops pattern Generated with [Devin](https://cli.devin.ai/docs) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
35bf51302c
commit
c39da91588
@ -31,6 +31,7 @@
|
|||||||
"@bytelyst/cosmos": "workspace:*",
|
"@bytelyst/cosmos": "workspace:*",
|
||||||
"@bytelyst/datastore": "workspace:*",
|
"@bytelyst/datastore": "workspace:*",
|
||||||
"@bytelyst/design-tokens": "workspace:*",
|
"@bytelyst/design-tokens": "workspace:*",
|
||||||
|
"@bytelyst/devops": "workspace:*",
|
||||||
"@bytelyst/errors": "workspace:*",
|
"@bytelyst/errors": "workspace:*",
|
||||||
"@bytelyst/extraction": "workspace:*",
|
"@bytelyst/extraction": "workspace:*",
|
||||||
"@bytelyst/logger": "workspace:*",
|
"@bytelyst/logger": "workspace:*",
|
||||||
|
|||||||
97
dashboards/admin-web/src/app/devops/page.tsx
Normal file
97
dashboards/admin-web/src/app/devops/page.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { DevopsPanel, type DevopsInfo } from '@bytelyst/devops/ui';
|
||||||
|
import { useAuth } from '@bytelyst/react-auth';
|
||||||
|
|
||||||
|
const bundleStartTime = Date.now();
|
||||||
|
|
||||||
|
function humanizeUptime(seconds: number): string {
|
||||||
|
if (seconds < 60) return `${seconds}s`;
|
||||||
|
const mins = Math.floor(seconds / 60);
|
||||||
|
if (mins < 60) return `${mins}m ${seconds % 60}s`;
|
||||||
|
const hrs = Math.floor(mins / 60);
|
||||||
|
if (hrs < 24) return `${hrs}h ${mins % 60}m`;
|
||||||
|
const days = Math.floor(hrs / 24);
|
||||||
|
return `${days}d ${hrs % 24}h ${mins % 60}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DevOpsPage() {
|
||||||
|
const { getAccessToken } = useAuth();
|
||||||
|
|
||||||
|
async function fetchBackendInfo(): Promise<DevopsInfo> {
|
||||||
|
const token = getAccessToken();
|
||||||
|
const platformUrl = process.env.NEXT_PUBLIC_PLATFORM_URL || 'http://localhost:4003';
|
||||||
|
const res = await fetch(`${platformUrl}/api/devops/info`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
const body = (await res.json().catch(() => ({}))) as { error?: string };
|
||||||
|
throw new Error(body?.error ?? `Backend devops info failed (${res.status})`);
|
||||||
|
}
|
||||||
|
return (await res.json()) as DevopsInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchWebInfo(): Promise<DevopsInfo> {
|
||||||
|
const env = process.env as Record<string, string | undefined>;
|
||||||
|
const builtAt = env.NEXT_PUBLIC_BYTELYST_BUILT_AT || null;
|
||||||
|
const startedAtMs = bundleStartTime;
|
||||||
|
const uptimeSec = Math.floor((Date.now() - startedAtMs) / 1000);
|
||||||
|
|
||||||
|
return {
|
||||||
|
build: {
|
||||||
|
commitSha: env.NEXT_PUBLIC_BYTELYST_COMMIT_SHA || null,
|
||||||
|
commitShaFull: env.NEXT_PUBLIC_BYTELYST_COMMIT_SHA_FULL || null,
|
||||||
|
branch: env.NEXT_PUBLIC_BYTELYST_BRANCH || null,
|
||||||
|
builtAt,
|
||||||
|
commitAuthor: env.NEXT_PUBLIC_BYTELYST_COMMIT_AUTHOR || null,
|
||||||
|
commitMessage: env.NEXT_PUBLIC_BYTELYST_COMMIT_MESSAGE || null,
|
||||||
|
dockerImage: env.NEXT_PUBLIC_BYTELYST_DOCKER_IMAGE || null,
|
||||||
|
},
|
||||||
|
runtime: {
|
||||||
|
uptimeSeconds: uptimeSec,
|
||||||
|
uptimeHuman: humanizeUptime(uptimeSec),
|
||||||
|
nodeVersion: 'browser',
|
||||||
|
platform: typeof window !== 'undefined' ? navigator.platform || 'unknown' : 'unknown',
|
||||||
|
arch: typeof window !== 'undefined' && navigator.userAgent.includes('arm') ? 'arm' : 'x86',
|
||||||
|
pid: 0,
|
||||||
|
hostname: typeof window !== 'undefined' ? window.location.hostname : 'unknown',
|
||||||
|
memoryMb: Math.round(
|
||||||
|
((performance as unknown as { memory?: { usedJSHeapSize?: number } })?.memory
|
||||||
|
?.usedJSHeapSize ?? 0) /
|
||||||
|
1024 /
|
||||||
|
1024
|
||||||
|
),
|
||||||
|
heapMb: Math.round(
|
||||||
|
((performance as unknown as { memory?: { usedJSHeapSize?: number } })?.memory
|
||||||
|
?.usedJSHeapSize ?? 0) /
|
||||||
|
1024 /
|
||||||
|
1024
|
||||||
|
),
|
||||||
|
startedAt: new Date(startedAtMs).toISOString(),
|
||||||
|
},
|
||||||
|
config: {
|
||||||
|
productId: env.NEXT_PUBLIC_PRODUCT_ID || 'admin',
|
||||||
|
serviceName: 'admin-web',
|
||||||
|
serviceVersion: '1.0.0',
|
||||||
|
nodeEnv: env.NODE_ENV || 'production',
|
||||||
|
envKeys: Object.keys(env)
|
||||||
|
.filter(k => /^NEXT_PUBLIC_/.test(k) && !/SECRET|KEY|TOKEN|PASSWORD/i.test(k))
|
||||||
|
.sort(),
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
platformUrl: env.NEXT_PUBLIC_PLATFORM_URL || 'http://localhost:4003',
|
||||||
|
userAgent: typeof window !== 'undefined' ? navigator.userAgent : 'unknown',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto py-8 px-4">
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">DevOps</h1>
|
||||||
|
<p className="text-sm text-gray-600">System information and deployment details</p>
|
||||||
|
</div>
|
||||||
|
<DevopsPanel fetchInfo={fetchBackendInfo} fetchWebInfo={fetchWebInfo} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -57,6 +57,7 @@ import {
|
|||||||
Bot,
|
Bot,
|
||||||
Globe,
|
Globe,
|
||||||
Download,
|
Download,
|
||||||
|
Server,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useAuth } from '@/lib/auth-context';
|
import { useAuth } from '@/lib/auth-context';
|
||||||
@ -114,6 +115,7 @@ const navItems = [
|
|||||||
{ href: '/ai-diagnostics', label: 'AI Diagnostics', icon: BrainCircuit },
|
{ href: '/ai-diagnostics', label: 'AI Diagnostics', icon: BrainCircuit },
|
||||||
{ href: '/feedback', label: 'User Feedback', icon: MessageSquare },
|
{ href: '/feedback', label: 'User Feedback', icon: MessageSquare },
|
||||||
{ href: '/ops/secrets', label: 'Secrets Manager', icon: KeyRound },
|
{ href: '/ops/secrets', label: 'Secrets Manager', icon: KeyRound },
|
||||||
|
{ href: '/devops', label: 'DevOps', icon: Server },
|
||||||
{ href: '/settings', label: 'Settings', icon: Settings },
|
{ href: '/settings', label: 'Settings', icon: Settings },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
"@bytelyst/config": "workspace:*",
|
"@bytelyst/config": "workspace:*",
|
||||||
"@bytelyst/cosmos": "workspace:*",
|
"@bytelyst/cosmos": "workspace:*",
|
||||||
"@bytelyst/datastore": "workspace:*",
|
"@bytelyst/datastore": "workspace:*",
|
||||||
|
"@bytelyst/devops": "workspace:*",
|
||||||
"@bytelyst/errors": "workspace:*",
|
"@bytelyst/errors": "workspace:*",
|
||||||
"@bytelyst/events": "workspace:*",
|
"@bytelyst/events": "workspace:*",
|
||||||
"@bytelyst/fastify-core": "workspace:*",
|
"@bytelyst/fastify-core": "workspace:*",
|
||||||
|
|||||||
@ -10,6 +10,12 @@
|
|||||||
|
|
||||||
// Resolve secrets from configured provider BEFORE config parsing
|
// Resolve secrets from configured provider BEFORE config parsing
|
||||||
import { resolveSecrets, LYSNR_SECRETS } from '@bytelyst/config';
|
import { resolveSecrets, LYSNR_SECRETS } from '@bytelyst/config';
|
||||||
|
import {
|
||||||
|
collectDevopsInfo,
|
||||||
|
getBuildInfo,
|
||||||
|
httpDependencyCheck,
|
||||||
|
readServiceVersion,
|
||||||
|
} from '@bytelyst/devops/server';
|
||||||
await resolveSecrets([
|
await resolveSecrets([
|
||||||
LYSNR_SECRETS.COSMOS_KEY,
|
LYSNR_SECRETS.COSMOS_KEY,
|
||||||
LYSNR_SECRETS.COSMOS_ENDPOINT,
|
LYSNR_SECRETS.COSMOS_ENDPOINT,
|
||||||
@ -281,6 +287,51 @@ await app.register(eventSubscriptionRoutes, { prefix: '/api' });
|
|||||||
// Agent executor + tool registry + scheduling + metrics
|
// Agent executor + tool registry + scheduling + metrics
|
||||||
await app.register(agentExecutorRoutes, { prefix: '/api' });
|
await app.register(agentExecutorRoutes, { prefix: '/api' });
|
||||||
|
|
||||||
|
// DevOps endpoints
|
||||||
|
await app.register(
|
||||||
|
async function (fastify) {
|
||||||
|
// DevOps version endpoint (public - no auth required)
|
||||||
|
fastify.get('/devops/version', async (request, reply) => {
|
||||||
|
return reply.send(getBuildInfo());
|
||||||
|
});
|
||||||
|
|
||||||
|
// DevOps info endpoint (admin only)
|
||||||
|
fastify.get(
|
||||||
|
'/devops/info',
|
||||||
|
{
|
||||||
|
preHandler: async (request, reply) => {
|
||||||
|
// Require admin role
|
||||||
|
const auth = (request as any).auth;
|
||||||
|
if (!auth || auth.role !== 'admin') {
|
||||||
|
return reply.code(403).send({ error: 'Admin access required' });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async (request, reply) => {
|
||||||
|
try {
|
||||||
|
const config = (await import('@bytelyst/config')).loadProductIdentity();
|
||||||
|
const info = await collectDevopsInfo({
|
||||||
|
productId: config.productId || 'platform',
|
||||||
|
serviceName: 'platform-service',
|
||||||
|
serviceVersion: readServiceVersion(import.meta.url),
|
||||||
|
dependencyChecks: [
|
||||||
|
() => httpDependencyCheck('cosmos-db', config.cosmosEndpoint || 'unknown'),
|
||||||
|
],
|
||||||
|
extra: {
|
||||||
|
platformServiceUrl: config.platformServiceUrl,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return reply.send(info);
|
||||||
|
} catch (error: any) {
|
||||||
|
fastify.log.error('Failed to collect devops info:', error);
|
||||||
|
return reply.code(500).send({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ prefix: '/api' }
|
||||||
|
);
|
||||||
|
|
||||||
// Register event bus subscribers
|
// Register event bus subscribers
|
||||||
registerDiagnosticsSubscribers(app.log);
|
registerDiagnosticsSubscribers(app.log);
|
||||||
registerDeliverySubscribers(app.log);
|
registerDeliverySubscribers(app.log);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user