mirror of
https://github.com/tips-of-mine/GLPI-Plugin-CVE-Prototype.git
synced 2025-06-27 22:58:45 +02:00
286 lines
11 KiB
TypeScript
286 lines
11 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Save, Plus, Trash2, RefreshCw } from 'lucide-react';
|
|
import { mockSources } from '../data/mockData';
|
|
import { CVESource } from '../types/cve';
|
|
|
|
const SourcesConfig: React.FC = () => {
|
|
const [sources, setSources] = useState<CVESource[]>(mockSources);
|
|
const [newSource, setNewSource] = useState<boolean>(false);
|
|
const [formData, setFormData] = useState<Partial<CVESource>>({
|
|
name: '',
|
|
url: '',
|
|
api_key: '',
|
|
is_active: true,
|
|
sync_frequency: 24
|
|
});
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
|
const { name, value, type } = e.target as HTMLInputElement;
|
|
setFormData({
|
|
...formData,
|
|
[name]: type === 'checkbox' ? (e.target as HTMLInputElement).checked :
|
|
type === 'number' ? parseInt(value) : value
|
|
});
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (formData.name && formData.url) {
|
|
const newSourceObj: CVESource = {
|
|
id: sources.length + 1,
|
|
name: formData.name || '',
|
|
url: formData.url || '',
|
|
api_key: formData.api_key || '',
|
|
is_active: formData.is_active || false,
|
|
sync_frequency: formData.sync_frequency || 24,
|
|
last_sync: 'Never',
|
|
sync_status: 'PENDING'
|
|
};
|
|
|
|
setSources([...sources, newSourceObj]);
|
|
setNewSource(false);
|
|
setFormData({
|
|
name: '',
|
|
url: '',
|
|
api_key: '',
|
|
is_active: true,
|
|
sync_frequency: 24
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleSync = (sourceId: number) => {
|
|
// In a real implementation, this would trigger the sync process
|
|
// For this prototype, we'll just update the last_sync time
|
|
setSources(
|
|
sources.map(source =>
|
|
source.id === sourceId
|
|
? {
|
|
...source,
|
|
last_sync: new Date().toISOString(),
|
|
sync_status: 'SUCCESS' as const
|
|
}
|
|
: source
|
|
)
|
|
);
|
|
};
|
|
|
|
const handleDelete = (sourceId: number) => {
|
|
setSources(sources.filter(source => source.id !== sourceId));
|
|
};
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<div className="mb-6">
|
|
<h1 className="text-2xl font-bold mb-1">CVE Data Sources</h1>
|
|
<p className="text-gray-600">Configure and manage vulnerability data sources</p>
|
|
</div>
|
|
|
|
<div className="mb-6 flex justify-between">
|
|
<p className="text-sm text-gray-500">
|
|
Configure external data sources for CVE information. The plugin will automatically sync with these sources based on the configured frequency.
|
|
</p>
|
|
<button
|
|
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 flex items-center"
|
|
onClick={() => setNewSource(true)}
|
|
>
|
|
<Plus className="h-4 w-4 mr-2" /> Add Source
|
|
</button>
|
|
</div>
|
|
|
|
{newSource && (
|
|
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
<h2 className="text-lg font-semibold mb-4">Add New Data Source</h2>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Source Name*
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={formData.name}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
API URL*
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="url"
|
|
value={formData.url}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
API Key (if required)
|
|
</label>
|
|
<input
|
|
type="password"
|
|
name="api_key"
|
|
value={formData.api_key}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Sync Frequency (hours)
|
|
</label>
|
|
<input
|
|
type="number"
|
|
name="sync_frequency"
|
|
value={formData.sync_frequency}
|
|
onChange={handleChange}
|
|
min="1"
|
|
max="168"
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
id="is_active"
|
|
name="is_active"
|
|
checked={formData.is_active}
|
|
onChange={handleChange}
|
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
|
/>
|
|
<label htmlFor="is_active" className="ml-2 block text-sm text-gray-900">
|
|
Active (enable synchronization)
|
|
</label>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Data Format
|
|
</label>
|
|
<select
|
|
name="data_format"
|
|
value={formData.data_format || 'JSON'}
|
|
onChange={handleChange}
|
|
className="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
|
>
|
|
<option value="JSON">JSON</option>
|
|
<option value="XML">XML</option>
|
|
<option value="CSV">CSV</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mt-6 flex justify-end space-x-3">
|
|
<button
|
|
type="button"
|
|
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
|
onClick={() => setNewSource(false)}
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 flex items-center"
|
|
>
|
|
<Save className="h-4 w-4 mr-2" /> Save Source
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
)}
|
|
|
|
<div className="bg-white shadow-md rounded-lg overflow-hidden">
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
<thead className="bg-gray-50">
|
|
<tr>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Source Name
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
URL
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Frequency
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Last Sync
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Status
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Active
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
{sources.map((source) => (
|
|
<tr key={source.id}>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
{source.name}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{source.url}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
Every {source.sync_frequency} hours
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{source.last_sync === 'Never'
|
|
? 'Never'
|
|
: new Date(source.last_sync).toLocaleString()}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
|
source.sync_status === 'SUCCESS' ? 'bg-green-100 text-green-800' :
|
|
source.sync_status === 'FAILED' ? 'bg-red-100 text-red-800' :
|
|
source.sync_status === 'IN_PROGRESS' ? 'bg-yellow-100 text-yellow-800' :
|
|
'bg-gray-100 text-gray-800'
|
|
}`}>
|
|
{source.sync_status}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
|
source.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
|
}`}>
|
|
{source.is_active ? 'Active' : 'Inactive'}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<button
|
|
className="text-blue-600 hover:text-blue-900 mr-3"
|
|
onClick={() => handleSync(source.id)}
|
|
>
|
|
<RefreshCw className="h-4 w-4" />
|
|
</button>
|
|
<button
|
|
className="text-red-600 hover:text-red-900"
|
|
onClick={() => handleDelete(source.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SourcesConfig; |