sync: malware page, security alerts, review diagnostics, skill detail improvements

- Add malware security advisory page and SecurityAlertBanner component
- Add review diagnostics API and reviewed stats page
- Improve skill detail page with better scoring display and metadata
- Update review API endpoints with enhanced filtering and stats
- Add skill file serving improvements and cache enhancements
- Remove legacy curation scripts (moved to internal tooling)
- Update CLI search with score filtering support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-31 16:08:12 +02:00
parent 3e22d2e238
commit 16ee9e3beb
33 changed files with 1231 additions and 3404 deletions

View File

@@ -8,7 +8,8 @@ import { SkillCard } from '@/components/SkillCard';
import { ReviewedSortSelector } from '@/components/ReviewedSortSelector';
import { getPageAlternates } from '@/lib/seo';
import { getOrSetCache, cacheKeys, cacheTTL } from '@/lib/cache';
import { Sparkles } from 'lucide-react';
import { Sparkles, BarChart3 } from 'lucide-react';
import Link from 'next/link';
// Force dynamic rendering to fetch fresh data from database
@@ -95,9 +96,16 @@ export default async function ReviewedPage({
</div>
<h1 className="hero-title mb-4">{t('title')}</h1>
<p className="hero-subtitle max-w-2xl mx-auto mb-6">{t('subtitle')}</p>
<p className="text-text-secondary text-sm max-w-3xl mx-auto leading-relaxed">
<p className="text-text-secondary text-sm max-w-3xl mx-auto leading-relaxed mb-4">
{t('explanation')}
</p>
<Link
href={`/${locale}/reviewed/stats`}
className="inline-flex items-center gap-1.5 text-sm text-primary-600 hover:text-primary-700 font-medium"
>
<BarChart3 className="w-4 h-4" />
{locale === 'fa' ? 'مشاهده آمار بررسی‌ها' : 'View Review Statistics'}
</Link>
</div>
</section>

View File

@@ -0,0 +1,224 @@
import { getTranslations, setRequestLocale } from 'next-intl/server';
import Link from 'next/link';
import { Header } from '@/components/Header';
import { Footer } from '@/components/Footer';
import { createDb, skillReviewQueries } from '@skillhub/db';
import { getPageAlternates } from '@/lib/seo';
import { getOrSetCache, cacheKeys, cacheTTL } from '@/lib/cache';
import { BarChart3, ShieldAlert, AlertTriangle, CheckCircle } from 'lucide-react';
export const dynamic = 'force-dynamic';
interface StatsData {
pipeline: Record<string, number>;
totalReviews: number;
scoreDistribution: Record<string, number>;
securityStats: Record<string, number>;
malwareCount: number;
}
async function getPublicReviewStats(): Promise<StatsData | null> {
try {
return await getOrSetCache(cacheKeys.reviewStatsPublic(), cacheTTL.reviewStatsPublic, async () => {
const db = createDb();
const [pipeline, totalReviews, scoreDistribution, securityStats, malwareCount] = await Promise.all([
skillReviewQueries.getPublicPipelineStats(db),
skillReviewQueries.countTotalReviews(db),
skillReviewQueries.getScoreDistribution(db),
skillReviewQueries.getSecurityStats(db),
skillReviewQueries.countMaliciousSkills(db),
]);
return { pipeline, totalReviews, scoreDistribution, securityStats, malwareCount };
});
} catch (error) {
console.error('Error fetching review stats:', error);
return null;
}
}
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
return {
alternates: getPageAlternates(locale, '/reviewed/stats'),
};
}
function StatCard({
icon: Icon,
label,
value,
color,
href,
}: {
icon: React.ElementType;
label: string;
value: number;
color: string;
href?: string;
}) {
const content = (
<div className={`flex items-center gap-3 p-4 rounded-xl border border-border bg-surface ${href ? 'hover:border-primary-300 transition-colors' : ''}`}>
<div className={`flex items-center justify-center w-10 h-10 rounded-lg ${color}`}>
<Icon className="w-5 h-5" />
</div>
<div>
<p className="text-2xl font-bold text-text-primary ltr-nums">{value.toLocaleString()}</p>
<p className="text-sm text-text-secondary">{label}</p>
</div>
</div>
);
if (href) {
return <Link href={href}>{content}</Link>;
}
return content;
}
function BarRow({
label,
value,
total,
color,
}: {
label: string;
value: number;
total: number;
color: string;
}) {
const pct = total > 0 ? (value / total) * 100 : 0;
return (
<div className="space-y-1">
<div className="flex items-center justify-between text-sm">
<span className="text-text-secondary">{label}</span>
<span className="font-medium text-text-primary ltr-nums">
{value.toLocaleString()} <span className="text-text-muted">({pct.toFixed(1)}%)</span>
</span>
</div>
<div className="h-3 rounded-full bg-surface-subtle overflow-hidden">
<div
className={`h-full rounded-full ${color} transition-all duration-500`}
style={{ width: `${Math.max(pct, 0.5)}%` }}
/>
</div>
</div>
);
}
export default async function ReviewStatsPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setRequestLocale(locale);
const t = await getTranslations('reviewStats');
const data = await getPublicReviewStats();
if (!data) {
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1 flex items-center justify-center">
<p className="text-text-muted">Failed to load stats.</p>
</main>
<Footer />
</div>
);
}
// Total = all browse-ready SKILL.md skills (for meaningful percentages)
const pipelineTotal = Object.values(data.pipeline).reduce((sum, n) => sum + n, 0);
const scoreTotal =
data.scoreDistribution.high +
data.scoreDistribution.mid +
data.scoreDistribution.low;
return (
<div className="min-h-screen flex flex-col">
<Header />
<main className="flex-1">
{/* Header Section */}
<section className="section-header bg-gradient-subtle">
<div className="container-main text-center">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-primary-50 text-primary-600 mb-4">
<BarChart3 className="w-7 h-7" />
</div>
<h1 className="hero-title mb-4">{t('title')}</h1>
<p className="hero-subtitle max-w-2xl mx-auto">{t('subtitle')}</p>
</div>
</section>
<section className="section bg-surface">
<div className="container-main max-w-4xl">
{/* Review Pipeline */}
<div className="card p-6 mb-8">
<h2 className="text-xl font-semibold text-text-primary mb-1">{t('pipelineTitle')}</h2>
<p className="text-sm text-text-muted mb-6">{t('pipelineDescription')}</p>
<div className="space-y-4">
<BarRow label={t('aiReviewed')} value={data.pipeline['ai-reviewed'] ?? 0} total={pipelineTotal} color="bg-primary-500" />
<BarRow label={t('needsReReview')} value={data.pipeline['needs-re-review'] ?? 0} total={pipelineTotal} color="bg-warning" />
</div>
<div className="mt-6 pt-4 border-t border-border flex items-center justify-between text-sm">
<span className="text-text-secondary">{t('totalReviews')}</span>
<span className="font-semibold text-text-primary ltr-nums">{data.totalReviews.toLocaleString()}</span>
</div>
</div>
{/* Score Distribution */}
<div className="card p-6 mb-8">
<h2 className="text-xl font-semibold text-text-primary mb-1">{t('scoreTitle')}</h2>
<p className="text-sm text-text-muted mb-6">{t('scoreDescription')}</p>
<div className="space-y-4">
<BarRow label={t('scoreHigh')} value={data.scoreDistribution.high} total={scoreTotal} color="bg-success" />
<BarRow label={t('scoreMid')} value={data.scoreDistribution.mid} total={scoreTotal} color="bg-gold" />
<BarRow label={t('scoreLow')} value={data.scoreDistribution.low} total={scoreTotal} color="bg-error" />
</div>
</div>
{/* Security & Malware */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{/* Security Scan */}
<div className="card p-6">
<h2 className="text-xl font-semibold text-text-primary mb-1">{t('securityTitle')}</h2>
<p className="text-sm text-text-muted mb-6">{t('securityDescription')}</p>
<div className="space-y-3">
<StatCard icon={CheckCircle} label={t('securityPass')} value={data.securityStats.pass} color="bg-success/10 text-success" />
<StatCard icon={AlertTriangle} label={t('securityWarning')} value={data.securityStats.warning} color="bg-warning/10 text-warning" />
<StatCard icon={ShieldAlert} label={t('securityFail')} value={data.securityStats.fail} color="bg-error/10 text-error" />
</div>
</div>
{/* Malware Detection */}
<div className="card p-6">
<h2 className="text-xl font-semibold text-text-primary mb-1">{t('malwareTitle')}</h2>
<p className="text-sm text-text-muted mb-6">{t('malwareDescription')}</p>
<StatCard
icon={ShieldAlert}
label={t('malwareFlagged')}
value={data.malwareCount}
color="bg-error/10 text-error"
href={`/${locale}/malware`}
/>
{data.malwareCount > 0 && (
<Link
href={`/${locale}/malware`}
className="inline-flex items-center gap-1.5 mt-4 text-sm text-primary-600 hover:text-primary-700 font-medium"
>
{t('malwareViewAll')}
</Link>
)}
</div>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
}