feat: update web and vuln list support filter
This commit is contained in:
@@ -48,12 +48,15 @@ export const login = (username, password) => {
|
||||
}
|
||||
|
||||
// 获取漏洞列表
|
||||
export const getVulnerabilities = (pageNo, pageSize, searchTerm = '') => {
|
||||
export const getVulnerabilities = (params) => {
|
||||
return api.get('/vulns', {
|
||||
params: {
|
||||
page_no: pageNo,
|
||||
page_size: pageSize,
|
||||
search: searchTerm,
|
||||
page_no: params.pageNo,
|
||||
page_size: params.pageSize,
|
||||
cve: params.cve,
|
||||
title: params.title,
|
||||
pushed: params.pushed,
|
||||
source: params.source,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { getVulnerabilities } from '../../lib/api'
|
||||
import { getVulnerabilities, getPlugins } from '../../lib/api'
|
||||
|
||||
const VulnerabilityListPage = () => {
|
||||
const [vulnerabilities, setVulnerabilities] = useState([])
|
||||
@@ -9,18 +9,50 @@ const VulnerabilityListPage = () => {
|
||||
const [pageNo, setPageNo] = useState(1)
|
||||
const [pageSize] = useState(10)
|
||||
const [totalCount, setTotalCount] = useState(0)
|
||||
const [searchTerm, setSearchTerm] = useState('')
|
||||
const [plugins, setPlugins] = useState([])
|
||||
// 新的筛选条件状态
|
||||
const [filters, setFilters] = useState({
|
||||
cve: '',
|
||||
title: '',
|
||||
pushed: '',
|
||||
source: '' // 存储插件的link字段
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
fetchPlugins()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
fetchVulnerabilities()
|
||||
}, [pageNo, searchTerm])
|
||||
}, [pageNo, filters])
|
||||
|
||||
const fetchPlugins = async () => {
|
||||
try {
|
||||
const response = await getPlugins()
|
||||
setPlugins(response.data.data)
|
||||
} catch (err) {
|
||||
console.error('获取插件列表失败:', err)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchVulnerabilities = async () => {
|
||||
setLoading(true)
|
||||
setError('')
|
||||
|
||||
try {
|
||||
const response = await getVulnerabilities(pageNo, pageSize, searchTerm)
|
||||
const params = {
|
||||
pageNo,
|
||||
pageSize,
|
||||
...filters
|
||||
}
|
||||
// 清理空值参数
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] === '' || params[key] === undefined) {
|
||||
delete params[key]
|
||||
}
|
||||
})
|
||||
|
||||
const response = await getVulnerabilities(params)
|
||||
const { data, total_count } = response.data.data
|
||||
setVulnerabilities(data)
|
||||
setTotalCount(total_count)
|
||||
@@ -35,9 +67,34 @@ const VulnerabilityListPage = () => {
|
||||
const handlePageChange = (newPageNo) => {
|
||||
setPageNo(newPageNo)
|
||||
}
|
||||
const handleFilterChange = (e) => {
|
||||
const { name, value } = e.target
|
||||
setFilters(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}))
|
||||
// 重置页码到第一页
|
||||
setPageNo(1)
|
||||
}
|
||||
|
||||
const handleSearch = (e) => {
|
||||
e.preventDefault()
|
||||
const handleSourceFilterChange = (e) => {
|
||||
const selectedLink = e.target.value
|
||||
setFilters(prev => ({
|
||||
...prev,
|
||||
source: selectedLink
|
||||
}))
|
||||
// 重置页码到第一页
|
||||
setPageNo(1)
|
||||
}
|
||||
|
||||
|
||||
const handleResetFilters = () => {
|
||||
setFilters({
|
||||
cve: '',
|
||||
title: '',
|
||||
pushed: '',
|
||||
source: ''
|
||||
})
|
||||
// 重置页码到第一页
|
||||
setPageNo(1)
|
||||
}
|
||||
@@ -55,27 +112,81 @@ const VulnerabilityListPage = () => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 搜索框 */}
|
||||
{/* 筛选控件 */}
|
||||
<div className="mb-6">
|
||||
<form onSubmit={handleSearch} className="max-w-md mx-auto">
|
||||
<div className="relative">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<div>
|
||||
<label htmlFor="cve" className="block text-sm font-medium text-gray-700">
|
||||
CVE编号
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
placeholder="搜索漏洞标题或描述..."
|
||||
name="cve"
|
||||
id="cve"
|
||||
value={filters.cve}
|
||||
onChange={handleFilterChange}
|
||||
placeholder="搜索CVE编号..."
|
||||
className="block w-full py-2 pl-4 pr-12 leading-5 placeholder-gray-500 bg-white border border-gray-300 rounded-md focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3"
|
||||
>
|
||||
<svg className="w-5 h-5 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div>
|
||||
<label htmlFor="title" className="block text-sm font-medium text-gray-700">
|
||||
漏洞标题
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
id="title"
|
||||
value={filters.title}
|
||||
onChange={handleFilterChange}
|
||||
placeholder="搜索漏洞标题..."
|
||||
className="block w-full py-2 pl-4 pr-12 leading-5 placeholder-gray-500 bg-white border border-gray-300 rounded-md focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="pushed" className="block text-sm font-medium text-gray-700">
|
||||
推送状态
|
||||
</label>
|
||||
<select
|
||||
name="pushed"
|
||||
id="pushed"
|
||||
value={filters.pushed}
|
||||
onChange={handleFilterChange}
|
||||
className="block w-full py-2 pl-4 pr-12 leading-5 placeholder-gray-500 bg-white border border-gray-300 rounded-md focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
>
|
||||
<option value="">全部</option>
|
||||
<option value="true">已推送</option>
|
||||
<option value="false">未推送</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="source" className="block text-sm font-medium text-gray-700">
|
||||
来源
|
||||
</label>
|
||||
<select
|
||||
name="source"
|
||||
id="source"
|
||||
value={filters.source}
|
||||
onChange={handleSourceFilterChange}
|
||||
className="block w-full py-2 pl-4 pr-12 leading-5 placeholder-gray-500 bg-white border border-gray-300 rounded-md focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
||||
>
|
||||
<option value="">全部</option>
|
||||
{plugins.map((plugin) => (
|
||||
<option key={plugin.link} value={plugin.link}>
|
||||
{plugin.display_name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end mt-4">
|
||||
<button
|
||||
onClick={handleResetFilters}
|
||||
className="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
||||
>
|
||||
重置筛选
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -19,4 +19,4 @@ dir = "logs"
|
||||
max_files = 64
|
||||
|
||||
[telemetry.logs.stderr]
|
||||
filter = "INFO"
|
||||
filter = "DEBUG"
|
||||
|
||||
@@ -60,14 +60,45 @@ impl fmt::Display for Severity {
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ListVulnInformationRequest {
|
||||
pub page_filter: PageFilter,
|
||||
pub search: Option<String>,
|
||||
pub search_params: SearchParams,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct SearchParams {
|
||||
pub cve: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub pushed: Option<bool>,
|
||||
pub source: Option<String>,
|
||||
}
|
||||
|
||||
impl SearchParams {
|
||||
pub fn new() -> Self {
|
||||
SearchParams::default()
|
||||
}
|
||||
|
||||
pub fn with_cve(mut self, cve: Option<String>) -> Self {
|
||||
self.cve = cve;
|
||||
self
|
||||
}
|
||||
pub fn with_title(mut self, title: Option<String>) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
pub fn with_pushed(mut self, pushed: Option<bool>) -> Self {
|
||||
self.pushed = pushed;
|
||||
self
|
||||
}
|
||||
pub fn with_source(mut self, source: Option<String>) -> Self {
|
||||
self.source = source;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ListVulnInformationRequest {
|
||||
pub fn new(page_filter: PageFilter, search: Option<String>) -> Self {
|
||||
pub fn new(page_filter: PageFilter, search_params: SearchParams) -> Self {
|
||||
ListVulnInformationRequest {
|
||||
page_filter,
|
||||
search,
|
||||
search_params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ pub trait VulnRepository: Clone + Send + Sync + 'static {
|
||||
&self,
|
||||
) -> impl Future<Output = Result<Option<SyncDataTask>, Error>> + Send;
|
||||
|
||||
fn list_vulnfusion_information(
|
||||
fn list_vuln_information(
|
||||
&self,
|
||||
req: ListVulnInformationRequest,
|
||||
) -> impl Future<Output = Result<ListVulnInformationResponseData, Error>> + Send;
|
||||
|
||||
@@ -54,7 +54,7 @@ where
|
||||
&self,
|
||||
req: ListVulnInformationRequest,
|
||||
) -> Result<ListVulnInformationResponseData, Error> {
|
||||
let ret = self.repo.list_vulnfusion_information(req).await?; // Implement the logic here
|
||||
let ret = self.repo.list_vuln_information(req).await?; // Implement the logic here
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
page_utils::{PageFilter, PageNo, PageNoError, PageSize, PageSizeError},
|
||||
vuln_information::{
|
||||
GetVulnInformationRequest, ListVulnInformationRequest,
|
||||
ListVulnInformationResponseData, VulnInformation,
|
||||
ListVulnInformationResponseData, SearchParams, VulnInformation,
|
||||
},
|
||||
},
|
||||
ports::VulnService,
|
||||
@@ -26,7 +26,10 @@ use crate::{
|
||||
pub struct ListVulnInformationRequestBody {
|
||||
pub page_no: i32,
|
||||
pub page_size: i32,
|
||||
pub search: Option<String>,
|
||||
pub cve: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub pushed: Option<bool>,
|
||||
pub source: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
|
||||
@@ -41,7 +44,12 @@ impl ListVulnInformationRequestBody {
|
||||
let page_no = PageNo::try_new(self.page_no)?;
|
||||
let page_size = PageSize::try_new(self.page_size)?;
|
||||
let page_filter = PageFilter::new(page_no, page_size);
|
||||
Ok(ListVulnInformationRequest::new(page_filter, self.search))
|
||||
let search_params = SearchParams::new()
|
||||
.with_cve(self.cve)
|
||||
.with_pushed(self.pushed)
|
||||
.with_source(self.source)
|
||||
.with_title(self.title);
|
||||
Ok(ListVulnInformationRequest::new(page_filter, search_params))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -266,6 +266,13 @@ impl<D: Dao> DaoQueryBuilder<D> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn and_where_bool(mut self, column: &str, value: bool) -> Self {
|
||||
let condition = Expr::col(Alias::new(column)).eq(value);
|
||||
self.query.and_where(condition.clone());
|
||||
self.conditions.push(Condition::all().add(condition));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn order_by_desc(mut self, column: &str) -> Self {
|
||||
self.query
|
||||
.order_by(Alias::new(column), sea_query::Order::Desc);
|
||||
|
||||
@@ -63,7 +63,7 @@ impl VulnRepository for Pg {
|
||||
Ok(sync_data_task)
|
||||
}
|
||||
|
||||
async fn list_vulnfusion_information(
|
||||
async fn list_vuln_information(
|
||||
&self,
|
||||
req: ListVulnInformationRequest,
|
||||
) -> Result<ListVulnInformationResponseData, Error> {
|
||||
@@ -74,11 +74,11 @@ impl VulnRepository for Pg {
|
||||
let vuln_informations = VulnInformationDao::filter_vulnfusion_information(
|
||||
&mut tx,
|
||||
&req.page_filter,
|
||||
req.search.as_deref(),
|
||||
&req.search_params,
|
||||
)
|
||||
.await?;
|
||||
let count =
|
||||
VulnInformationDao::filter_vulnfusion_information_count(&mut tx, req.search.as_deref())
|
||||
VulnInformationDao::filter_vulnfusion_information_count(&mut tx, &req.search_params)
|
||||
.await?;
|
||||
|
||||
tx.commit()
|
||||
|
||||
@@ -4,7 +4,7 @@ use sqlx::{Postgres, Transaction};
|
||||
use crate::{
|
||||
domain::models::{
|
||||
page_utils::PageFilter,
|
||||
vuln_information::{CreateVulnInformation, VulnInformation},
|
||||
vuln_information::{CreateVulnInformation, SearchParams, VulnInformation},
|
||||
},
|
||||
errors::Error,
|
||||
output::db::base::{
|
||||
@@ -106,22 +106,29 @@ impl VulnInformationDao {
|
||||
pub async fn filter_vulnfusion_information(
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
page_filter: &PageFilter,
|
||||
search: Option<&str>,
|
||||
search_params: &SearchParams,
|
||||
) -> Result<Vec<VulnInformation>, Error> {
|
||||
let mut query_builder = DaoQueryBuilder::<Self>::new();
|
||||
|
||||
if let Some(title) = &search_params.title {
|
||||
query_builder = query_builder.and_where_like("title", title);
|
||||
}
|
||||
|
||||
if let Some(source) = &search_params.source {
|
||||
query_builder = query_builder.and_where_like("source", source);
|
||||
}
|
||||
|
||||
if let Some(pushed) = &search_params.pushed {
|
||||
query_builder = query_builder.and_where_bool("pushed", *pushed);
|
||||
}
|
||||
|
||||
if let Some(cve) = &search_params.cve {
|
||||
query_builder = query_builder.and_where_like("cve", cve);
|
||||
}
|
||||
|
||||
let page_no = *page_filter.page_no().as_ref();
|
||||
let page_size = *page_filter.page_size().as_ref();
|
||||
let offset = (page_no - 1) * page_size;
|
||||
|
||||
// 添加搜索条件
|
||||
if let Some(search_term) = search
|
||||
&& !search_term.is_empty()
|
||||
{
|
||||
query_builder = query_builder
|
||||
.and_where_like("title", search_term)
|
||||
.and_where_like("description", search_term);
|
||||
}
|
||||
|
||||
query_builder
|
||||
.order_by_desc("updated_at")
|
||||
.limit_offset(page_size as i64, offset as i64)
|
||||
@@ -131,17 +138,24 @@ impl VulnInformationDao {
|
||||
|
||||
pub async fn filter_vulnfusion_information_count(
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
search: Option<&str>,
|
||||
search_params: &SearchParams,
|
||||
) -> Result<i64, Error> {
|
||||
let mut query_builder = DaoQueryBuilder::<Self>::new();
|
||||
|
||||
// 添加搜索条件
|
||||
if let Some(search_term) = search
|
||||
&& !search_term.is_empty()
|
||||
{
|
||||
query_builder = query_builder
|
||||
.and_where_like("title", search_term)
|
||||
.and_where_like("description", search_term);
|
||||
if let Some(title) = &search_params.title {
|
||||
query_builder = query_builder.and_where_like("title", title);
|
||||
}
|
||||
|
||||
if let Some(source) = &search_params.source {
|
||||
query_builder = query_builder.and_where_like("source", source);
|
||||
}
|
||||
|
||||
if let Some(pushed) = &search_params.pushed {
|
||||
query_builder = query_builder.and_where_bool("pushed", *pushed);
|
||||
}
|
||||
|
||||
if let Some(cve) = &search_params.cve {
|
||||
query_builder = query_builder.and_where_like("cve", cve);
|
||||
}
|
||||
|
||||
query_builder.count(tx).await
|
||||
|
||||
Reference in New Issue
Block a user