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:
@@ -21,6 +21,7 @@ export function Header() {
|
||||
{ name: t('home'), href: `/${locale}` },
|
||||
{ name: t('browse'), href: `/${locale}/browse` },
|
||||
{ name: t('categories'), href: `/${locale}/categories` },
|
||||
{ name: t('reviewed'), href: `/${locale}/reviewed` },
|
||||
{ name: t('docs'), href: `/${locale}/docs` },
|
||||
];
|
||||
|
||||
|
||||
52
apps/web/components/SecurityAlertBanner.tsx
Normal file
52
apps/web/components/SecurityAlertBanner.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { X, ShieldAlert } from 'lucide-react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
const BLOG_POST_URL = 'https://blog.palebluedot.live/2026/03/19/malware-openclaw-skills-security-advisory/';
|
||||
const STORAGE_KEY = 'security-alert-openclaw-dismissed';
|
||||
|
||||
export function SecurityAlertBanner() {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const t = useTranslations('securityAlert');
|
||||
|
||||
useEffect(() => {
|
||||
const dismissed = localStorage.getItem(STORAGE_KEY);
|
||||
if (!dismissed) {
|
||||
setIsVisible(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDismiss = () => {
|
||||
setIsVisible(false);
|
||||
localStorage.setItem(STORAGE_KEY, 'true');
|
||||
};
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="relative bg-error-bg border-b-2 border-error">
|
||||
<div className="container-main py-2.5 px-4 flex items-center justify-center gap-2 text-sm">
|
||||
<ShieldAlert className="w-4 h-4 text-error flex-shrink-0" />
|
||||
<span className="font-semibold text-error">{t('label')}</span>
|
||||
<span className="text-text-primary">{t('text')}</span>
|
||||
<a
|
||||
href={BLOG_POST_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-error underline underline-offset-2 hover:text-error/80 transition-colors font-medium"
|
||||
>
|
||||
{t('link')}
|
||||
</a>
|
||||
<button
|
||||
onClick={handleDismiss}
|
||||
className="absolute end-2 sm:end-4 p-1 hover:bg-error/10 rounded transition-colors"
|
||||
aria-label={t('dismiss')}
|
||||
>
|
||||
<X className="w-4 h-4 text-error" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import Link from 'next/link';
|
||||
import { Star, Download, Shield, CheckCircle, Clock, RefreshCw, Sparkles } from 'lucide-react';
|
||||
import { Star, Download, Shield, CheckCircle, Clock, RefreshCw, Sparkles, XCircle, ShieldAlert } from 'lucide-react';
|
||||
import { formatCompactNumber } from '@/lib/format-number';
|
||||
|
||||
const FORMAT_BADGE_LABELS: Record<string, string> = {
|
||||
@@ -27,6 +27,8 @@ interface SkillCardProps {
|
||||
sourceFormat?: string | null;
|
||||
createdAt?: Date | string | null;
|
||||
updatedAt?: Date | string | null;
|
||||
reviewOutdated?: boolean | null;
|
||||
isMalicious?: boolean | null;
|
||||
};
|
||||
locale: string;
|
||||
/** Show time badge (for New Skills page) */
|
||||
@@ -56,16 +58,21 @@ export function SkillCard({ skill, locale, showTimeBadge, formatTimeAgo }: Skill
|
||||
|
||||
const showRating = (skill.ratingCount ?? 0) >= 3;
|
||||
|
||||
// AI review score badge (only for reviewed skills with score >= 50)
|
||||
// AI review score badge (for all reviewed skills)
|
||||
const getAiScoreBadge = () => {
|
||||
const rs = skill.reviewStatus;
|
||||
if (!rs || rs === 'unreviewed' || rs === 'auto-scored') return null;
|
||||
const score = skill.aiScore ?? skill.latestAiScore;
|
||||
if (!score || score < 50) return null;
|
||||
if (score == null) return null;
|
||||
if (score === 0) {
|
||||
return { score: 0, color: 'text-error bg-error/10 border-error/20', rejected: true };
|
||||
}
|
||||
const color = score >= 75
|
||||
? 'text-success bg-success/10 border-success/20'
|
||||
: 'text-gold bg-gold/10 border-gold/20';
|
||||
return { score, color };
|
||||
: score >= 50
|
||||
? 'text-gold bg-gold/10 border-gold/20'
|
||||
: 'text-text-muted bg-surface-subtle border-border';
|
||||
return { score, color, rejected: false };
|
||||
};
|
||||
const aiScoreBadge = getAiScoreBadge();
|
||||
|
||||
@@ -117,12 +124,28 @@ export function SkillCard({ skill, locale, showTimeBadge, formatTimeAgo }: Skill
|
||||
{skill.description}
|
||||
</p>
|
||||
|
||||
{/* Metadata Row: AI Score + Stars + Downloads + Rating */}
|
||||
{/* Metadata Row: Malware / AI Score + Stars + Downloads + Rating */}
|
||||
<div className="flex flex-wrap items-center gap-4 text-sm text-text-muted mb-2">
|
||||
{aiScoreBadge && (
|
||||
{skill.isMalicious && (
|
||||
<span className="flex items-center gap-1 px-1.5 py-0.5 text-xs font-bold rounded border text-error bg-error/10 border-error/20">
|
||||
<ShieldAlert className="w-3 h-3" />
|
||||
<span>{locale === 'fa' ? 'بدافزار' : 'Malware'}</span>
|
||||
</span>
|
||||
)}
|
||||
{!skill.isMalicious && aiScoreBadge && (
|
||||
<span className={`flex items-center gap-1 px-1.5 py-0.5 text-xs font-medium rounded border ${aiScoreBadge.color}`}>
|
||||
<Sparkles className="w-3 h-3" />
|
||||
<span className="ltr-nums">{aiScoreBadge.score}</span>
|
||||
{aiScoreBadge.rejected ? (
|
||||
<>
|
||||
<XCircle className="w-3 h-3" />
|
||||
<span>{locale === 'fa' ? 'رد شده' : 'Rejected'}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Sparkles className="w-3 h-3" />
|
||||
<span className="ltr-nums">{aiScoreBadge.score}</span>
|
||||
{skill.reviewOutdated && <span className="text-warning" title="Review based on previous version">⚠</span>}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<span className="flex items-center gap-1">
|
||||
|
||||
Reference in New Issue
Block a user