mirror of
https://github.com/tips-of-mine/GLPI-Plugin-CVE-Prototype.git
synced 2025-06-27 22:58:45 +02:00
362 lines
15 KiB
TypeScript
362 lines
15 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Save, Plus, Trash2 } from 'lucide-react';
|
|
import { mockRules } from '../data/mockData';
|
|
import { CVERule } from '../types/cve';
|
|
|
|
const RulesConfig: React.FC = () => {
|
|
const [rules, setRules] = useState<CVERule[]>(mockRules);
|
|
const [editingRule, setEditingRule] = useState<Partial<CVERule> | null>(null);
|
|
const [isEditing, setIsEditing] = useState<boolean>(false);
|
|
|
|
const handleEditRule = (rule: CVERule) => {
|
|
setEditingRule({ ...rule });
|
|
setIsEditing(true);
|
|
};
|
|
|
|
const handleNewRule = () => {
|
|
setEditingRule({
|
|
id: rules.length + 1,
|
|
name: '',
|
|
criteria: { severity: 'HIGH' },
|
|
actions: { create_ticket: true, ticket_priority: 'NORMAL' },
|
|
priority: rules.length + 1,
|
|
is_active: true
|
|
});
|
|
setIsEditing(true);
|
|
};
|
|
|
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
|
|
const { name, value, type } = e.target as HTMLInputElement;
|
|
|
|
if (name === 'severity' || name === 'ticket_priority') {
|
|
setEditingRule({
|
|
...editingRule,
|
|
criteria: name === 'severity' ? { ...editingRule?.criteria, severity: value } : editingRule?.criteria,
|
|
actions: name === 'ticket_priority' ? { ...editingRule?.actions, ticket_priority: value } : editingRule?.actions
|
|
});
|
|
return;
|
|
}
|
|
|
|
setEditingRule({
|
|
...editingRule,
|
|
[name]: type === 'checkbox'
|
|
? (e.target as HTMLInputElement).checked
|
|
: type === 'number'
|
|
? parseInt(value)
|
|
: value
|
|
});
|
|
};
|
|
|
|
const handleActionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const { name, checked } = e.target;
|
|
|
|
if (editingRule) {
|
|
setEditingRule({
|
|
...editingRule,
|
|
actions: {
|
|
...editingRule.actions,
|
|
[name]: checked
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (editingRule && editingRule.name) {
|
|
if (editingRule.id && rules.some(r => r.id === editingRule.id)) {
|
|
// Update existing rule
|
|
setRules(rules.map(rule => rule.id === editingRule.id ? editingRule as CVERule : rule));
|
|
} else {
|
|
// Add new rule
|
|
setRules([...rules, editingRule as CVERule]);
|
|
}
|
|
|
|
setIsEditing(false);
|
|
setEditingRule(null);
|
|
}
|
|
};
|
|
|
|
const handleDelete = (ruleId: number) => {
|
|
setRules(rules.filter(rule => rule.id !== ruleId));
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setIsEditing(false);
|
|
setEditingRule(null);
|
|
};
|
|
|
|
return (
|
|
<div className="p-6">
|
|
<div className="mb-6">
|
|
<h1 className="text-2xl font-bold mb-1">CVE Processing Rules</h1>
|
|
<p className="text-gray-600">Configure automated actions for vulnerability management</p>
|
|
</div>
|
|
|
|
<div className="mb-6 flex justify-between">
|
|
<p className="text-sm text-gray-500">
|
|
Define rules that automatically process CVEs based on specific criteria. Rules are evaluated in priority order.
|
|
</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={handleNewRule}
|
|
>
|
|
<Plus className="h-4 w-4 mr-2" /> Add Rule
|
|
</button>
|
|
</div>
|
|
|
|
{isEditing && editingRule && (
|
|
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
|
<h2 className="text-lg font-semibold mb-4">
|
|
{editingRule.id && rules.some(r => r.id === editingRule.id) ? 'Edit Rule' : 'Add New Rule'}
|
|
</h2>
|
|
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div className="col-span-2">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Rule Name*
|
|
</label>
|
|
<input
|
|
type="text"
|
|
name="name"
|
|
value={editingRule.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>
|
|
<h3 className="text-md font-medium mb-3">Criteria</h3>
|
|
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Severity
|
|
</label>
|
|
<select
|
|
name="severity"
|
|
value={editingRule.criteria?.severity}
|
|
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="CRITICAL">Critical</option>
|
|
<option value="HIGH">High</option>
|
|
<option value="MEDIUM">Medium</option>
|
|
<option value="LOW">Low</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Priority
|
|
</label>
|
|
<input
|
|
type="number"
|
|
name="priority"
|
|
value={editingRule.priority}
|
|
onChange={handleChange}
|
|
min="1"
|
|
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"
|
|
/>
|
|
<p className="mt-1 text-xs text-gray-500">Lower numbers are processed first</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<h3 className="text-md font-medium mb-3">Actions</h3>
|
|
|
|
<div className="mb-4 flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
id="create_ticket"
|
|
name="create_ticket"
|
|
checked={!!editingRule.actions?.create_ticket}
|
|
onChange={handleActionChange}
|
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
|
/>
|
|
<label htmlFor="create_ticket" className="ml-2 block text-sm text-gray-900">
|
|
Create ticket automatically
|
|
</label>
|
|
</div>
|
|
|
|
{editingRule.actions?.create_ticket && (
|
|
<div className="mb-4 ml-6">
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
Ticket Priority
|
|
</label>
|
|
<select
|
|
name="ticket_priority"
|
|
value={editingRule.actions?.ticket_priority}
|
|
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="VERY HIGH">Very High</option>
|
|
<option value="HIGH">High</option>
|
|
<option value="NORMAL">Normal</option>
|
|
<option value="LOW">Low</option>
|
|
</select>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mb-4 flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
id="notify_admins"
|
|
name="notify_admins"
|
|
checked={!!editingRule.actions?.notify_admins}
|
|
onChange={handleActionChange}
|
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
|
/>
|
|
<label htmlFor="notify_admins" className="ml-2 block text-sm text-gray-900">
|
|
Send email notification
|
|
</label>
|
|
</div>
|
|
|
|
<div className="mb-4 flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
id="add_to_report"
|
|
name="add_to_report"
|
|
checked={!!editingRule.actions?.add_to_report}
|
|
onChange={handleActionChange}
|
|
className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded"
|
|
/>
|
|
<label htmlFor="add_to_report" className="ml-2 block text-sm text-gray-900">
|
|
Add to vulnerability report
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="col-span-2 flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
id="is_active"
|
|
name="is_active"
|
|
checked={!!editingRule.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 this rule)
|
|
</label>
|
|
</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={handleCancel}
|
|
>
|
|
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 Rule
|
|
</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">
|
|
Priority
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Rule Name
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Criteria
|
|
</th>
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Actions
|
|
</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-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
Actions
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
{rules.map((rule) => (
|
|
<tr key={rule.id} className="hover:bg-gray-50">
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
|
{rule.priority}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
|
{rule.name}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{rule.criteria.severity && (
|
|
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
|
rule.criteria.severity === 'CRITICAL' ? 'bg-red-100 text-red-800' :
|
|
rule.criteria.severity === 'HIGH' ? 'bg-orange-100 text-orange-800' :
|
|
rule.criteria.severity === 'MEDIUM' ? 'bg-yellow-100 text-yellow-800' :
|
|
'bg-blue-100 text-blue-800'
|
|
}`}>
|
|
{rule.criteria.severity}
|
|
</span>
|
|
)}
|
|
{rule.criteria.system_tags && (
|
|
<span className="ml-2 inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800">
|
|
System Tags
|
|
</span>
|
|
)}
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
{rule.actions.create_ticket && (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-purple-100 text-purple-800 mr-1">
|
|
Create Ticket
|
|
</span>
|
|
)}
|
|
{rule.actions.notify_admins && (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800 mr-1">
|
|
Notify
|
|
</span>
|
|
)}
|
|
{rule.actions.add_to_report && (
|
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800">
|
|
Report
|
|
</span>
|
|
)}
|
|
</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 ${
|
|
rule.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'
|
|
}`}>
|
|
{rule.is_active ? 'Active' : 'Inactive'}
|
|
</span>
|
|
</td>
|
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
<button
|
|
className="text-indigo-600 hover:text-indigo-900 mr-3"
|
|
onClick={() => handleEditRule(rule)}
|
|
>
|
|
Edit
|
|
</button>
|
|
<button
|
|
className="text-red-600 hover:text-red-900"
|
|
onClick={() => handleDelete(rule.id)}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RulesConfig; |