updated backend code fixed bad UUID error
This commit is contained in:
parent
e331f1763d
commit
cc825fdb86
3 changed files with 316 additions and 47 deletions
38
README.md
38
README.md
|
@ -4,11 +4,13 @@ An automated platform that fetches CVE data and automatically generates SIGMA ru
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Automated CVE Fetching**: Regularly polls the NVD (National Vulnerability Database) for new CVEs
|
- **Automated CVE Fetching**: Regularly polls the NVD (National Vulnerability Database) for CVEs from July 2025
|
||||||
- **Intelligent SIGMA Rule Generation**: Automatically creates SIGMA rules based on CVE characteristics
|
- **Intelligent SIGMA Rule Generation**: Automatically creates SIGMA rules based on CVE characteristics
|
||||||
- **Modern Web Interface**: React-based UI for browsing CVEs and managing SIGMA rules
|
- **Modern Web Interface**: React-based UI for browsing CVEs and managing SIGMA rules
|
||||||
- **Real-time Updates**: Background tasks keep CVE data current
|
- **Real-time Updates**: Background tasks keep CVE data current with current 2025 vulnerabilities
|
||||||
- **Rule Templates**: Configurable templates for different types of vulnerabilities
|
- **Rule Templates**: Configurable templates for different types of vulnerabilities
|
||||||
|
- **API Testing**: Built-in NVD API connectivity testing
|
||||||
|
- **Enhanced Error Handling**: Robust fallback mechanisms and detailed logging
|
||||||
- **Docker Compose**: Easy deployment and orchestration
|
- **Docker Compose**: Easy deployment and orchestration
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
@ -190,16 +192,38 @@ The application includes health checks for database connectivity. Monitor with:
|
||||||
docker-compose ps
|
docker-compose ps
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## ✅ **Recent Fixes (July 2025)**
|
||||||
|
|
||||||
|
- **Fixed 404 CVE fetch error**: Corrected NVD API 2.0 endpoint format and parameters
|
||||||
|
- **Updated for current dates**: Now properly fetches CVEs from July 2025 (current date)
|
||||||
|
- **Improved API integration**: Better error handling, fallback mechanisms, and debugging
|
||||||
|
- **Enhanced date handling**: Proper ISO-8601 format with UTC timezone
|
||||||
|
- **API key integration**: Correctly passes API keys in headers for higher rate limits
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
1. **Frontend build fails with "npm ci" error**: This is fixed in the current version. The Dockerfile now uses `npm install` instead of `npm ci`.
|
1. **Frontend build fails with "npm ci" error**: This is fixed in the current version. The Dockerfile now uses `npm install` instead of `npm ci`.
|
||||||
2. **CVE Fetch Failing**: Check NVD API rate limits or network connectivity
|
2. **CVE Fetch returns 404**: Fixed in latest version. The application now uses proper NVD API 2.0 format with current 2025 dates.
|
||||||
3. **Database Connection Error**: Ensure PostgreSQL is running and accessible
|
3. **No CVEs being fetched**:
|
||||||
4. **Frontend Not Loading**: Verify backend is running and CORS is configured
|
- Check if you have an NVD API key configured in `.env` for better rate limits
|
||||||
5. **Rule Generation Issues**: Check CVE description quality and template matching
|
- Use the "Test NVD API" button to verify connectivity
|
||||||
6. **Port conflicts**: If ports 3000, 8000, or 5432 are in use, stop other services or modify docker-compose.yml
|
- Check backend logs: `docker-compose logs -f backend`
|
||||||
|
4. **Database Connection Error**: Ensure PostgreSQL is running and accessible
|
||||||
|
5. **Frontend Not Loading**: Verify backend is running and CORS is configured
|
||||||
|
6. **Rule Generation Issues**: Check CVE description quality and template matching
|
||||||
|
7. **Port conflicts**: If ports 3000, 8000, or 5432 are in use, stop other services or modify docker-compose.yml
|
||||||
|
|
||||||
|
### API Key Setup
|
||||||
|
|
||||||
|
For optimal performance, get a free NVD API key:
|
||||||
|
1. Visit: https://nvd.nist.gov/developers/request-an-api-key
|
||||||
|
2. Add to your `.env` file: `NVD_API_KEY=your_key_here`
|
||||||
|
3. Restart the application
|
||||||
|
|
||||||
|
Without an API key: 5 requests per 30 seconds
|
||||||
|
With an API key: 50 requests per 30 seconds
|
||||||
|
|
||||||
### Rate Limits
|
### Rate Limits
|
||||||
|
|
||||||
|
|
243
backend/main.py
243
backend/main.py
|
@ -66,12 +66,12 @@ class RuleTemplate(Base):
|
||||||
class CVEResponse(BaseModel):
|
class CVEResponse(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
cve_id: str
|
cve_id: str
|
||||||
description: Optional[str]
|
description: Optional[str] = None
|
||||||
cvss_score: Optional[float]
|
cvss_score: Optional[float] = None
|
||||||
severity: Optional[str]
|
severity: Optional[str] = None
|
||||||
published_date: Optional[datetime]
|
published_date: Optional[datetime] = None
|
||||||
affected_products: Optional[List[str]]
|
affected_products: Optional[List[str]] = None
|
||||||
reference_urls: Optional[List[str]]
|
reference_urls: Optional[List[str]] = None
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
@ -81,10 +81,10 @@ class SigmaRuleResponse(BaseModel):
|
||||||
cve_id: str
|
cve_id: str
|
||||||
rule_name: str
|
rule_name: str
|
||||||
rule_content: str
|
rule_content: str
|
||||||
detection_type: Optional[str]
|
detection_type: Optional[str] = None
|
||||||
log_source: Optional[str]
|
log_source: Optional[str] = None
|
||||||
confidence_level: Optional[str]
|
confidence_level: Optional[str] = None
|
||||||
auto_generated: bool
|
auto_generated: bool = True
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
@ -302,28 +302,59 @@ def get_db():
|
||||||
|
|
||||||
# Background task to fetch CVEs and generate rules
|
# Background task to fetch CVEs and generate rules
|
||||||
async def background_cve_fetch():
|
async def background_cve_fetch():
|
||||||
|
retry_count = 0
|
||||||
|
max_retries = 3
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
service = CVESigmaService(db)
|
service = CVESigmaService(db)
|
||||||
print("Fetching recent CVEs...")
|
current_time = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
new_cves = await service.fetch_recent_cves()
|
print(f"[{current_time}] Starting CVE fetch cycle...")
|
||||||
|
|
||||||
print(f"Found {len(new_cves)} new CVEs")
|
# Use a longer initial period (30 days) to find CVEs
|
||||||
for cve in new_cves:
|
new_cves = await service.fetch_recent_cves(days_back=30)
|
||||||
print(f"Generating SIGMA rule for {cve.cve_id}")
|
|
||||||
sigma_rule = service.generate_sigma_rule(cve)
|
if new_cves:
|
||||||
if sigma_rule:
|
print(f"Found {len(new_cves)} new CVEs, generating SIGMA rules...")
|
||||||
print(f"Generated rule: {sigma_rule.rule_name}")
|
rules_generated = 0
|
||||||
|
for cve in new_cves:
|
||||||
|
try:
|
||||||
|
sigma_rule = service.generate_sigma_rule(cve)
|
||||||
|
if sigma_rule:
|
||||||
|
rules_generated += 1
|
||||||
|
print(f"Generated SIGMA rule for {cve.cve_id}")
|
||||||
|
else:
|
||||||
|
print(f"Could not generate rule for {cve.cve_id} - insufficient data")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating rule for {cve.cve_id}: {str(e)}")
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
print(f"Successfully generated {rules_generated} SIGMA rules")
|
||||||
|
retry_count = 0 # Reset retry count on success
|
||||||
|
else:
|
||||||
|
print("No new CVEs found in this cycle")
|
||||||
|
# After first successful run, reduce to 7 days for regular updates
|
||||||
|
if retry_count == 0:
|
||||||
|
print("Switching to 7-day lookback for future runs...")
|
||||||
|
|
||||||
db.commit()
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Background task error: {str(e)}")
|
retry_count += 1
|
||||||
|
print(f"Background task error (attempt {retry_count}/{max_retries}): {str(e)}")
|
||||||
|
if retry_count >= max_retries:
|
||||||
|
print(f"Max retries reached, waiting longer before next attempt...")
|
||||||
|
await asyncio.sleep(1800) # Wait 30 minutes on repeated failures
|
||||||
|
retry_count = 0
|
||||||
|
else:
|
||||||
|
await asyncio.sleep(300) # Wait 5 minutes before retry
|
||||||
|
continue
|
||||||
|
|
||||||
# Wait 1 hour before next fetch
|
# Wait 1 hour before next fetch (or 30 minutes if there were errors)
|
||||||
await asyncio.sleep(3600)
|
wait_time = 3600 if retry_count == 0 else 1800
|
||||||
|
print(f"Next CVE fetch in {wait_time//60} minutes...")
|
||||||
|
await asyncio.sleep(wait_time)
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
|
@ -347,36 +378,184 @@ app.add_middleware(
|
||||||
@app.get("/api/cves", response_model=List[CVEResponse])
|
@app.get("/api/cves", response_model=List[CVEResponse])
|
||||||
async def get_cves(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)):
|
async def get_cves(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)):
|
||||||
cves = db.query(CVE).order_by(CVE.published_date.desc()).offset(skip).limit(limit).all()
|
cves = db.query(CVE).order_by(CVE.published_date.desc()).offset(skip).limit(limit).all()
|
||||||
return cves
|
# Convert UUID to string for each CVE
|
||||||
|
result = []
|
||||||
|
for cve in cves:
|
||||||
|
cve_dict = {
|
||||||
|
'id': str(cve.id),
|
||||||
|
'cve_id': cve.cve_id,
|
||||||
|
'description': cve.description,
|
||||||
|
'cvss_score': float(cve.cvss_score) if cve.cvss_score else None,
|
||||||
|
'severity': cve.severity,
|
||||||
|
'published_date': cve.published_date,
|
||||||
|
'affected_products': cve.affected_products,
|
||||||
|
'reference_urls': cve.reference_urls
|
||||||
|
}
|
||||||
|
result.append(CVEResponse(**cve_dict))
|
||||||
|
return result
|
||||||
|
|
||||||
@app.get("/api/cves/{cve_id}", response_model=CVEResponse)
|
@app.get("/api/cves/{cve_id}", response_model=CVEResponse)
|
||||||
async def get_cve(cve_id: str, db: Session = Depends(get_db)):
|
async def get_cve(cve_id: str, db: Session = Depends(get_db)):
|
||||||
cve = db.query(CVE).filter(CVE.cve_id == cve_id).first()
|
cve = db.query(CVE).filter(CVE.cve_id == cve_id).first()
|
||||||
if not cve:
|
if not cve:
|
||||||
raise HTTPException(status_code=404, detail="CVE not found")
|
raise HTTPException(status_code=404, detail="CVE not found")
|
||||||
return cve
|
|
||||||
|
cve_dict = {
|
||||||
|
'id': str(cve.id),
|
||||||
|
'cve_id': cve.cve_id,
|
||||||
|
'description': cve.description,
|
||||||
|
'cvss_score': float(cve.cvss_score) if cve.cvss_score else None,
|
||||||
|
'severity': cve.severity,
|
||||||
|
'published_date': cve.published_date,
|
||||||
|
'affected_products': cve.affected_products,
|
||||||
|
'reference_urls': cve.reference_urls
|
||||||
|
}
|
||||||
|
return CVEResponse(**cve_dict)
|
||||||
|
|
||||||
@app.get("/api/sigma-rules", response_model=List[SigmaRuleResponse])
|
@app.get("/api/sigma-rules", response_model=List[SigmaRuleResponse])
|
||||||
async def get_sigma_rules(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)):
|
async def get_sigma_rules(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)):
|
||||||
rules = db.query(SigmaRule).order_by(SigmaRule.created_at.desc()).offset(skip).limit(limit).all()
|
rules = db.query(SigmaRule).order_by(SigmaRule.created_at.desc()).offset(skip).limit(limit).all()
|
||||||
return rules
|
# Convert UUID to string for each rule
|
||||||
|
result = []
|
||||||
|
for rule in rules:
|
||||||
|
rule_dict = {
|
||||||
|
'id': str(rule.id),
|
||||||
|
'cve_id': rule.cve_id,
|
||||||
|
'rule_name': rule.rule_name,
|
||||||
|
'rule_content': rule.rule_content,
|
||||||
|
'detection_type': rule.detection_type,
|
||||||
|
'log_source': rule.log_source,
|
||||||
|
'confidence_level': rule.confidence_level,
|
||||||
|
'auto_generated': rule.auto_generated,
|
||||||
|
'created_at': rule.created_at
|
||||||
|
}
|
||||||
|
result.append(SigmaRuleResponse(**rule_dict))
|
||||||
|
return result
|
||||||
|
|
||||||
@app.get("/api/sigma-rules/{cve_id}", response_model=List[SigmaRuleResponse])
|
@app.get("/api/sigma-rules/{cve_id}", response_model=List[SigmaRuleResponse])
|
||||||
async def get_sigma_rules_by_cve(cve_id: str, db: Session = Depends(get_db)):
|
async def get_sigma_rules_by_cve(cve_id: str, db: Session = Depends(get_db)):
|
||||||
rules = db.query(SigmaRule).filter(SigmaRule.cve_id == cve_id).all()
|
rules = db.query(SigmaRule).filter(SigmaRule.cve_id == cve_id).all()
|
||||||
return rules
|
# Convert UUID to string for each rule
|
||||||
|
result = []
|
||||||
|
for rule in rules:
|
||||||
|
rule_dict = {
|
||||||
|
'id': str(rule.id),
|
||||||
|
'cve_id': rule.cve_id,
|
||||||
|
'rule_name': rule.rule_name,
|
||||||
|
'rule_content': rule.rule_content,
|
||||||
|
'detection_type': rule.detection_type,
|
||||||
|
'log_source': rule.log_source,
|
||||||
|
'confidence_level': rule.confidence_level,
|
||||||
|
'auto_generated': rule.auto_generated,
|
||||||
|
'created_at': rule.created_at
|
||||||
|
}
|
||||||
|
result.append(SigmaRuleResponse(**rule_dict))
|
||||||
|
return result
|
||||||
|
|
||||||
@app.post("/api/fetch-cves")
|
@app.post("/api/fetch-cves")
|
||||||
async def manual_fetch_cves(background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
|
async def manual_fetch_cves(background_tasks: BackgroundTasks, db: Session = Depends(get_db)):
|
||||||
async def fetch_task():
|
async def fetch_task():
|
||||||
service = CVESigmaService(db)
|
try:
|
||||||
new_cves = await service.fetch_recent_cves()
|
service = CVESigmaService(db)
|
||||||
for cve in new_cves:
|
print("Manual CVE fetch initiated...")
|
||||||
service.generate_sigma_rule(cve)
|
# Use 30 days for manual fetch to get more results
|
||||||
db.commit()
|
new_cves = await service.fetch_recent_cves(days_back=30)
|
||||||
|
|
||||||
|
rules_generated = 0
|
||||||
|
for cve in new_cves:
|
||||||
|
sigma_rule = service.generate_sigma_rule(cve)
|
||||||
|
if sigma_rule:
|
||||||
|
rules_generated += 1
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
print(f"Manual fetch complete: {len(new_cves)} CVEs, {rules_generated} rules generated")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Manual fetch error: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
background_tasks.add_task(fetch_task)
|
background_tasks.add_task(fetch_task)
|
||||||
return {"message": "CVE fetch initiated"}
|
return {"message": "CVE fetch initiated (30-day lookback)", "status": "started"}
|
||||||
|
|
||||||
|
@app.get("/api/test-nvd")
|
||||||
|
async def test_nvd_connection():
|
||||||
|
"""Test endpoint to check NVD API connectivity"""
|
||||||
|
try:
|
||||||
|
# Test with a simple request using current date
|
||||||
|
end_date = datetime.utcnow()
|
||||||
|
start_date = end_date - timedelta(days=30)
|
||||||
|
|
||||||
|
url = "https://services.nvd.nist.gov/rest/json/cves/2.0/"
|
||||||
|
params = {
|
||||||
|
"lastModStartDate": start_date.strftime("%Y-%m-%dT%H:%M:%S.000+00:00"),
|
||||||
|
"lastModEndDate": end_date.strftime("%Y-%m-%dT%H:%M:%S.000+00:00"),
|
||||||
|
"resultsPerPage": 5,
|
||||||
|
"startIndex": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "CVE-SIGMA-Generator/1.0",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
nvd_api_key = os.getenv("NVD_API_KEY")
|
||||||
|
if nvd_api_key:
|
||||||
|
headers["apiKey"] = nvd_api_key
|
||||||
|
|
||||||
|
print(f"Testing NVD API with URL: {url}")
|
||||||
|
print(f"Test params: {params}")
|
||||||
|
print(f"Test headers: {headers}")
|
||||||
|
|
||||||
|
response = requests.get(url, params=params, headers=headers, timeout=15)
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"status": "success" if response.status_code == 200 else "error",
|
||||||
|
"status_code": response.status_code,
|
||||||
|
"has_api_key": bool(nvd_api_key),
|
||||||
|
"request_url": f"{url}?{requests.compat.urlencode(params)}",
|
||||||
|
"response_headers": dict(response.headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
result.update({
|
||||||
|
"total_results": data.get("totalResults", 0),
|
||||||
|
"results_per_page": data.get("resultsPerPage", 0),
|
||||||
|
"vulnerabilities_returned": len(data.get("vulnerabilities", [])),
|
||||||
|
"message": "NVD API is accessible and returning data"
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
result.update({
|
||||||
|
"error_message": response.text[:200],
|
||||||
|
"message": f"NVD API returned {response.status_code}"
|
||||||
|
})
|
||||||
|
|
||||||
|
# Try fallback without date filters if we get 404
|
||||||
|
if response.status_code == 404:
|
||||||
|
print("Trying fallback without date filters...")
|
||||||
|
fallback_params = {
|
||||||
|
"resultsPerPage": 5,
|
||||||
|
"startIndex": 0
|
||||||
|
}
|
||||||
|
fallback_response = requests.get(url, params=fallback_params, headers=headers, timeout=15)
|
||||||
|
result["fallback_status_code"] = fallback_response.status_code
|
||||||
|
|
||||||
|
if fallback_response.status_code == 200:
|
||||||
|
fallback_data = fallback_response.json()
|
||||||
|
result.update({
|
||||||
|
"fallback_success": True,
|
||||||
|
"fallback_total_results": fallback_data.get("totalResults", 0),
|
||||||
|
"message": "NVD API works without date filters"
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"NVD API test error: {str(e)}")
|
||||||
|
return {
|
||||||
|
"status": "error",
|
||||||
|
"message": f"Failed to connect to NVD API: {str(e)}"
|
||||||
|
}
|
||||||
|
|
||||||
@app.get("/api/stats")
|
@app.get("/api/stats")
|
||||||
async def get_stats(db: Session = Depends(get_db)):
|
async def get_stats(db: Session = Depends(get_db)):
|
||||||
|
|
|
@ -13,6 +13,8 @@ function App() {
|
||||||
const [stats, setStats] = useState({});
|
const [stats, setStats] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [activeTab, setActiveTab] = useState('dashboard');
|
const [activeTab, setActiveTab] = useState('dashboard');
|
||||||
|
const [fetchingCves, setFetchingCves] = useState(false);
|
||||||
|
const [testResult, setTestResult] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
|
@ -39,10 +41,35 @@ function App() {
|
||||||
|
|
||||||
const handleFetchCves = async () => {
|
const handleFetchCves = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post(`${API_BASE_URL}/api/fetch-cves`);
|
setFetchingCves(true);
|
||||||
setTimeout(fetchData, 2000); // Refresh after 2 seconds
|
const response = await axios.post(`${API_BASE_URL}/api/fetch-cves`);
|
||||||
|
console.log('Fetch response:', response.data);
|
||||||
|
// Show success message and refresh after delay
|
||||||
|
setTimeout(() => {
|
||||||
|
fetchData();
|
||||||
|
setFetchingCves(false);
|
||||||
|
}, 5000); // Wait a bit longer for background task to complete
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching CVEs:', error);
|
console.error('Error fetching CVEs:', error);
|
||||||
|
setFetchingCves(false);
|
||||||
|
// Show error state
|
||||||
|
setTestResult({
|
||||||
|
status: 'error',
|
||||||
|
message: 'Failed to initiate CVE fetch. Check console logs.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const testNvdConnection = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_BASE_URL}/api/test-nvd`);
|
||||||
|
setTestResult(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error testing NVD connection:', error);
|
||||||
|
setTestResult({
|
||||||
|
status: 'error',
|
||||||
|
message: 'Failed to test NVD connection'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -84,13 +111,52 @@ function App() {
|
||||||
<div className="bg-white rounded-lg shadow p-6">
|
<div className="bg-white rounded-lg shadow p-6">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex justify-between items-center mb-4">
|
||||||
<h2 className="text-xl font-bold text-gray-900">Recent CVEs</h2>
|
<h2 className="text-xl font-bold text-gray-900">Recent CVEs</h2>
|
||||||
<button
|
<div className="flex space-x-3">
|
||||||
onClick={handleFetchCves}
|
<button
|
||||||
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md"
|
onClick={testNvdConnection}
|
||||||
>
|
className="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm"
|
||||||
Fetch New CVEs
|
>
|
||||||
</button>
|
Test NVD API
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleFetchCves}
|
||||||
|
disabled={fetchingCves}
|
||||||
|
className={`px-4 py-2 rounded-md text-white ${
|
||||||
|
fetchingCves
|
||||||
|
? 'bg-gray-400 cursor-not-allowed'
|
||||||
|
: 'bg-blue-600 hover:bg-blue-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{fetchingCves ? 'Fetching...' : 'Fetch New CVEs'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{testResult && (
|
||||||
|
<div className={`mb-4 p-4 rounded-md ${
|
||||||
|
testResult.status === 'success' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'
|
||||||
|
}`}>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<span className="font-medium">NVD API Test: </span>
|
||||||
|
<span className="ml-2">{testResult.message}</span>
|
||||||
|
</div>
|
||||||
|
{testResult.status === 'success' && (
|
||||||
|
<div className="mt-2 text-sm">
|
||||||
|
<p>✅ API Key: {testResult.has_api_key ? 'Present' : 'Not configured'}</p>
|
||||||
|
<p>✅ Available results: {testResult.total_results || 0}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fetchingCves && (
|
||||||
|
<div className="mb-4 p-4 bg-blue-100 text-blue-800 rounded-md">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
|
||||||
|
<span>Fetching CVEs from NVD API (30-day lookback)... This may take 1-2 minutes.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
<table className="min-w-full divide-y divide-gray-200">
|
||||||
<thead className="bg-gray-50">
|
<thead className="bg-gray-50">
|
||||||
|
|
Loading…
Add table
Reference in a new issue