mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-01-31 23:01:11 +03:00
feat: add version information component and integrate version check in health API
This commit is contained in:
@@ -2,6 +2,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { ExternalLink } from "lucide-react";
|
import { ExternalLink } from "lucide-react";
|
||||||
import { links } from "@/data/Sidebar";
|
import { links } from "@/data/Sidebar";
|
||||||
|
import { VersionInfo } from "./VersionInfo";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
@@ -19,7 +20,7 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={cn("w-64 border-r bg-background", className)}>
|
<aside className={cn("w-64 border-r bg-background", className)}>
|
||||||
<div className="flex flex-col h-full py-4">
|
<div className="flex flex-col h-full pt-4">
|
||||||
<nav className="flex flex-col gap-y-1 pl-2 pr-3">
|
<nav className="flex flex-col gap-y-1 pl-2 pr-3">
|
||||||
{links.map((link, index) => {
|
{links.map((link, index) => {
|
||||||
const isActive = currentPath === link.href;
|
const isActive = currentPath === link.href;
|
||||||
@@ -59,6 +60,7 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
<ExternalLink className="h-3 w-3" />
|
<ExternalLink className="h-3 w-3" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<VersionInfo />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
49
src/components/layout/VersionInfo.tsx
Normal file
49
src/components/layout/VersionInfo.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { healthApi } from "@/lib/api";
|
||||||
|
|
||||||
|
export function VersionInfo() {
|
||||||
|
const [versionInfo, setVersionInfo] = useState<{
|
||||||
|
current: string;
|
||||||
|
latest: string;
|
||||||
|
updateAvailable: boolean;
|
||||||
|
}>({
|
||||||
|
current: "loading...",
|
||||||
|
latest: "",
|
||||||
|
updateAvailable: false
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchVersion = async () => {
|
||||||
|
try {
|
||||||
|
const healthData = await healthApi.check();
|
||||||
|
setVersionInfo({
|
||||||
|
current: healthData.version || "unknown",
|
||||||
|
latest: healthData.latestVersion || "unknown",
|
||||||
|
updateAvailable: healthData.updateAvailable || false
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch version:", error);
|
||||||
|
setVersionInfo({
|
||||||
|
current: "unknown",
|
||||||
|
latest: "",
|
||||||
|
updateAvailable: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchVersion();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="text-xs text-muted-foreground text-center pt-2 pb-3 border-t border-border mt-2">
|
||||||
|
{versionInfo.updateAvailable ? (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span>v{versionInfo.current}</span>
|
||||||
|
<span className="text-primary">v{versionInfo.latest} available</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span>v{versionInfo.current}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -94,6 +94,8 @@ export interface HealthResponse {
|
|||||||
status: "ok" | "error";
|
status: "ok" | "error";
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
latestVersion: string;
|
||||||
|
updateAvailable: boolean;
|
||||||
database: {
|
database: {
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -147,6 +149,8 @@ export const healthApi = {
|
|||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
error: error instanceof Error ? error.message : "Unknown error checking health",
|
error: error instanceof Error ? error.message : "Unknown error checking health",
|
||||||
version: "unknown",
|
version: "unknown",
|
||||||
|
latestVersion: "unknown",
|
||||||
|
updateAvailable: false,
|
||||||
database: { connected: false, message: "Failed to connect to API" },
|
database: { connected: false, message: "Failed to connect to API" },
|
||||||
system: {
|
system: {
|
||||||
uptime: { startTime: "", uptimeMs: 0, formatted: "N/A" },
|
uptime: { startTime: "", uptimeMs: 0, formatted: "N/A" },
|
||||||
|
|||||||
@@ -3,10 +3,20 @@ import { jsonResponse } from "@/lib/utils";
|
|||||||
import { db } from "@/lib/db";
|
import { db } from "@/lib/db";
|
||||||
import { ENV } from "@/lib/config";
|
import { ENV } from "@/lib/config";
|
||||||
import os from "os";
|
import os from "os";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
// Track when the server started
|
// Track when the server started
|
||||||
const serverStartTime = new Date();
|
const serverStartTime = new Date();
|
||||||
|
|
||||||
|
// Cache for the latest version to avoid frequent GitHub API calls
|
||||||
|
interface VersionCache {
|
||||||
|
latestVersion: string;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
let versionCache: VersionCache | null = null;
|
||||||
|
const CACHE_TTL = 3600000; // 1 hour in milliseconds
|
||||||
|
|
||||||
export const GET: APIRoute = async () => {
|
export const GET: APIRoute = async () => {
|
||||||
try {
|
try {
|
||||||
// Check database connection by running a simple query
|
// Check database connection by running a simple query
|
||||||
@@ -24,11 +34,19 @@ export const GET: APIRoute = async () => {
|
|||||||
env: ENV.NODE_ENV,
|
env: ENV.NODE_ENV,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get current and latest versions
|
||||||
|
const currentVersion = process.env.npm_package_version || "unknown";
|
||||||
|
const latestVersion = await checkLatestVersion();
|
||||||
|
|
||||||
// Build response
|
// Build response
|
||||||
const healthData = {
|
const healthData = {
|
||||||
status: "ok",
|
status: "ok",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
version: process.env.npm_package_version || "unknown",
|
version: currentVersion,
|
||||||
|
latestVersion: latestVersion,
|
||||||
|
updateAvailable: latestVersion !== "unknown" &&
|
||||||
|
currentVersion !== "unknown" &&
|
||||||
|
latestVersion !== currentVersion,
|
||||||
database: dbStatus,
|
database: dbStatus,
|
||||||
system: systemInfo,
|
system: systemInfo,
|
||||||
};
|
};
|
||||||
@@ -45,6 +63,9 @@ export const GET: APIRoute = async () => {
|
|||||||
status: "error",
|
status: "error",
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
error: error instanceof Error ? error.message : "Unknown error",
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
version: process.env.npm_package_version || "unknown",
|
||||||
|
latestVersion: "unknown",
|
||||||
|
updateAvailable: false,
|
||||||
},
|
},
|
||||||
status: 503, // Service Unavailable
|
status: 503, // Service Unavailable
|
||||||
});
|
});
|
||||||
@@ -122,5 +143,37 @@ function formatBytes(bytes: number): string {
|
|||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for the latest version from GitHub releases
|
||||||
|
*/
|
||||||
|
async function checkLatestVersion(): Promise<string> {
|
||||||
|
// Return cached version if available and not expired
|
||||||
|
if (versionCache && (Date.now() - versionCache.timestamp) < CACHE_TTL) {
|
||||||
|
return versionCache.latestVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch the latest release from GitHub
|
||||||
|
const response = await axios.get(
|
||||||
|
'https://api.github.com/repos/arunavo4/gitea-mirror/releases/latest',
|
||||||
|
{ headers: { 'Accept': 'application/vnd.github.v3+json' } }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract version from tag_name (remove 'v' prefix if present)
|
||||||
|
const latestVersion = response.data.tag_name.replace(/^v/, '');
|
||||||
|
|
||||||
|
// Update cache
|
||||||
|
versionCache = {
|
||||||
|
latestVersion,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return latestVersion;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to check for latest version:', error);
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Import sql tag for raw SQL queries
|
// Import sql tag for raw SQL queries
|
||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
|
|||||||
Reference in New Issue
Block a user