Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support clear request log #16

Merged
merged 2 commits into from
Feb 8, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/lynx-core/src/proxy/https_proxy.rs
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ pub async fn https_proxy(
.serve_connection_with_upgrades(TokioIo::new(stream), service)
.await
{
error!("HTTPS proxy connect error: {}", err.to_string());
error!("HTTPS proxy connect error: {:?}", err);
}
}
Err(e) => {
70 changes: 52 additions & 18 deletions crates/lynx-core/src/self_service/mod.rs
Original file line number Diff line number Diff line change
@@ -15,14 +15,15 @@ use http_body_util::{BodyExt, StreamBody};
use hyper::body::{Frame, Incoming};
use hyper::{Request, Response};
use schemars::schema_for;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use sea_orm::{ColumnTrait, EntityTrait, ModelTrait, QueryFilter};
use tokio::fs::File;
use tokio_stream::wrappers::BroadcastStream;
use tokio_stream::wrappers::{BroadcastStream, ReadDirStream};
use tokio_util::io::ReaderStream;
use tracing::{debug, error};
use tracing::{debug, error, trace};
use tracing_subscriber::fmt::format;
use utils::{
internal_server_error, operation_error, parse_query_params, response_ok, validate_error,
OperationError, ValidateError,
internal_server_error, not_found, operation_error, parse_query_params, response_ok,
validate_error, OperationError, ValidateError,
};

pub mod api;
@@ -35,14 +36,17 @@ pub const RULE_GROUP_ADD: &str = "/__self_service_path__/rule_group/add";
pub const RULE_GROUP_UPDATE: &str = "/__self_service_path__/rule_group/update";
pub const RULE_GROUP_DELETE: &str = "/__self_service_path__/rule_group/delete";
pub const RULE_GROUP_LIST: &str = "/__self_service_path__/rule_group/list";

pub const RULE_ADD: &str = "/__self_service_path__/rule/add";
pub const RULE_UPDATE: &str = "/__self_service_path__/rule/update";
pub const RULE_DELETE: &str = "/__self_service_path__/rule/delete";
pub const RULE_DETAIL: &str = "/__self_service_path__/rule";
pub const RULE_CONTEXT_SCHEMA: &str = "/__self_service_path__/rule/context/schema";

pub const REQUEST_CLEAR: &str = "/__self_service_path__/request/clear";
pub const REQUEST_LOG: &str = "/__self_service_path__/request_log";
pub const REQUEST_BODY: &str = "/__self_service_path__/request_body";

pub const RESPONSE: &str = "/__self_service_path__/response";
pub const RESPONSE_BODY: &str = "/__self_service_path__/response_body";

@@ -55,14 +59,15 @@ pub fn match_self_service(req: &Request<Incoming>) -> bool {
req.uri().path().starts_with(SELF_SERVICE_PATH_PREFIX)
}

pub async fn handle_self_service(
pub async fn self_service_router(
req: Request<Incoming>,
) -> Result<Response<BoxBody<Bytes, Error>>> {
let method = req.method();
let path = req.uri().path();
debug!("handle_self_service: method: {:?}, path: {}", method, path);

let res = match (method, path) {
trace!("self_service_router: method: {:?}, path: {}", method, path);

match (method, path) {
(&method::Method::GET, HELLO_PATH) => {
return Ok(Response::new(full(Bytes::from("Hello, World!"))));
}
@@ -143,6 +148,30 @@ pub async fn handle_self_service(

return Ok(res);
}
(&method::Method::POST, REQUEST_CLEAR) => {
trace!("clear request and response data");
let db = DB.get().unwrap();
request::Entity::delete_many().exec(db).await?;
response::Entity::delete_many().exec(db).await?;

trace!("clear raw data");
let raw_root_dir = &APP_CONFIG.get().unwrap().raw_root_dir;
trace!("clear raw data: {}", raw_root_dir.display());
let entries = tokio::fs::read_dir(raw_root_dir)
.await
.map_err(|e| anyhow!(e).context(format!("clear raw data error")))?;

let read_dir_stream = ReadDirStream::new(entries);
read_dir_stream
.for_each(|entry| async {
if let Ok(path) = entry {
let p = path.path();
tokio::fs::remove_dir_all(p).await.unwrap();
}
})
.await;
return response_ok::<Option<()>>(None);
}
(&method::Method::GET, RESPONSE) => {
let params: HashMap<String, String> = req
.uri()
@@ -304,8 +333,12 @@ pub async fn handle_self_service(

return Ok(res);
}

(&method::Method::GET, path) if path.starts_with(SELF_SERVICE_PATH_PREFIX) => {
(&method::Method::GET, path)
if path == SELF_SERVICE_PATH_PREFIX
|| path == &format!("{}/", SELF_SERVICE_PATH_PREFIX)
|| path == &format!("{}/index.html", SELF_SERVICE_PATH_PREFIX)
|| path == &format!("{}/static", SELF_SERVICE_PATH_PREFIX) =>
{
let mut static_path = &path[SELF_SERVICE_PATH_PREFIX.len()..];
if static_path.starts_with("/") {
static_path = &static_path[1..];
@@ -331,7 +364,7 @@ pub async fn handle_self_service(

let static_file = static_file;
if static_file.is_err() {
return Err(anyhow!(OperationError::new("file not found".to_string())));
return Ok(not_found());
}
let static_file = static_file.unwrap();

@@ -347,14 +380,15 @@ pub async fn handle_self_service(
}

_ => {
let res = Response::builder()
.status(http::status::StatusCode::NOT_FOUND)
.header(CONTENT_TYPE, "text/plain")
.body(full(Bytes::from("Not Found")))
.unwrap();
return Ok(res);
return Ok(not_found());
}
};
}
}

pub async fn handle_self_service(
req: Request<Incoming>,
) -> Result<Response<BoxBody<Bytes, Error>>> {
let res = self_service_router(req).await;

match res {
Ok(res) => Ok(res),
5 changes: 2 additions & 3 deletions crates/lynx-core/src/self_service/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ use hyper::body::Incoming;
use hyper::Response;
use schemars::schema::RootSchema;

use crate::utils::full;
use crate::utils::{empty, full};

pub async fn parse_body_params<Value>(body: Incoming, schema: RootSchema) -> Result<Value>
where
@@ -143,7 +143,6 @@ where
pub fn not_found() -> Response<BoxBody<Bytes, Error>> {
return Response::builder()
.status(http::status::StatusCode::NOT_FOUND)
.header(CONTENT_TYPE, "text/plain")
.body(full(Bytes::from("Not Found")))
.body(empty())
.unwrap();
}
17 changes: 16 additions & 1 deletion crates/lynx-proxy/src/App.tsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,12 @@ import {
import { StyleProvider } from '@ant-design/cssinjs';
import { routeTree } from './routeTree.gen';
import { ConfigProvider, theme } from 'antd';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import {
MutationCache,
QueryCache,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';

const hashHistory = createHashHistory();
// Set up a Router instance
@@ -30,6 +35,16 @@ const queryClient = new QueryClient({
retry: false,
},
},
queryCache: new QueryCache({
onError: (error) => {
console.log(error);
},
}),
mutationCache: new MutationCache({
onError: (error) => {
console.log(error);
},
}),
});

const App = () => {
4 changes: 2 additions & 2 deletions crates/lynx-proxy/src/api/app.ts
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ export const useChangeRecordStatus = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: ({ status }: { status: RecordStatusEnum }) =>
mutationFn: async ({ status }: { status: RecordStatusEnum }) =>
fetch(`/__self_service_path__/app_config/record_status`, {
method: 'POST',
body: JSON.stringify({ status }),
@@ -25,7 +25,7 @@ export const useChangeRecordStatus = () => {
export const useGetAppConfig = () => {
return useQuery({
queryKey: ['/__self_service_path__/app_config'],
queryFn: () =>
queryFn: async () =>
fetch(`/__self_service_path__/app_config`).then(
(res) => res.json() as Promise<IAppConfigResponse>,
),
19 changes: 14 additions & 5 deletions crates/lynx-proxy/src/api/request.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { message } from 'antd';
import { IRequestModel, IResponseBoxView } from './models';
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import queryString from 'query-string';

export function fetchRequest(cb: (data: { add: IRequestModel }) => void) {
@@ -27,7 +27,7 @@ export function fetchRequest(cb: (data: { add: IRequestModel }) => void) {
}
const json = JSON.parse(line);
console.log('json', json);

cb(json);
});
} catch (e) {
@@ -43,7 +43,7 @@ export function fetchRequest(cb: (data: { add: IRequestModel }) => void) {
export const useGetRequestBodyQuery = (params: { id?: number }) => {
return useQuery({
queryKey: ['/__self_service_path__/request_body', params],
queryFn: () =>
queryFn: async () =>
fetch(
`/__self_service_path__/request_body?${queryString.stringify(params)}`,
).then((res) => res.blob().then((blob) => blob.arrayBuffer())),
@@ -54,7 +54,7 @@ export const useGetRequestBodyQuery = (params: { id?: number }) => {
export const useGetResponseQuery = (params: { requestId?: number }) => {
return useQuery({
queryKey: ['/__self_service_path__/response', params],
queryFn: () =>
queryFn: async () =>
fetch(
`/__self_service_path__/response?${queryString.stringify(params)}`,
).then((res) => res.json() as Promise<IResponseBoxView>),
@@ -65,9 +65,18 @@ export const useGetResponseQuery = (params: { requestId?: number }) => {
export const useGetResponseBodyQuery = (params: { requestId?: number }) => {
return useQuery({
queryKey: ['/__self_service_path__/response_body', params],
queryFn: () =>
queryFn: async () =>
fetch(
`/__self_service_path__/response_body?${queryString.stringify(params)}`,
).then((res) => res.blob().then((blob) => blob.arrayBuffer())),
});
};

export const useClearRequestLog = () => {
return useMutation({
mutationFn: async () =>
fetch(`/__self_service_path__/request/clear`, {
method: 'POST',
}).then((res) => res.json()),
});
};
6 changes: 3 additions & 3 deletions crates/lynx-proxy/src/api/rule.ts
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@ import queryString from 'query-string';
export const useGetRuleTreeQuery = () => {
return useQuery({
queryKey: ['/__self_service_path__/rule_group/list'],
queryFn: () =>
queryFn: async () =>
fetch(`/__self_service_path__/rule_group/list`).then(
(res) => res.json() as Promise<IRuleGroupTreeResponse>,
),
@@ -18,7 +18,7 @@ export const useGetRuleTreeQuery = () => {
export const useGetRuleDetailQuery = (params: { id?: number }) => {
return useQuery({
queryKey: ['/__self_service_path__/rule', params],
queryFn: () =>
queryFn: async () =>
fetch(
`/__self_service_path__/rule?${queryString.stringify(params)}`,
).then((res) => res.json() as Promise<IRuleContentResponse>),
@@ -29,7 +29,7 @@ export const useGetRuleDetailQuery = (params: { id?: number }) => {
export const useAddRuleGroup = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params: { name: string }) =>
mutationFn: async (params: { name: string }) =>
fetch(`/__self_service_path__/rule_group/add`, {
method: 'POST',
body: JSON.stringify(params),
2 changes: 1 addition & 1 deletion crates/lynx-proxy/src/components/SideBar/index.tsx
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ export const SideBar: React.FC = (_props) => {
const navigate = useNavigate();

return (
<div className="pt-4 w-11 flex justify-center">
<div className="pt-4 w-11 flex justify-center shadow-sm shadow-slate-400">
<Space direction="vertical" className="w-full">
<Button
type="text"
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { useClearRequestLog } from '@/api/request';
import { RiBrush2Line } from '@remixicon/react';
import { Button, message } from 'antd';
import React from 'react';

export const CleanRequestButton: React.FC = () => {
const { mutateAsync: clearRequestLog, isPending } = useClearRequestLog();

return (
<Button
type="text"
loading={isPending}
disabled={isPending}
size="small"
onClick={async () => {
await clearRequestLog();
message.success('Request log cleared');
}}
icon={<RiBrush2Line size={16} className="text-yellow-700" />}
>
Clear
</Button>
);
};
Original file line number Diff line number Diff line change
@@ -13,11 +13,11 @@ export const RecordingStatusButton: React.FC<
const changeRecordStatus = useChangeRecordStatus();

const recordingStatus = appConfigData?.data?.recordingStatus;
console.log('recordingStatus',appConfigData, recordingStatus);
return (
<div>
<Button
type="text"
size='small'
onClick={() => {
if (recordingStatus === RecordStatusEnum.StartRecording) {
changeRecordStatus.mutateAsync({
@@ -31,7 +31,7 @@ export const RecordingStatusButton: React.FC<
}}
icon={
<RiRecordCircleFill
size={18}
size={16}
color={
recordingStatus === RecordStatusEnum.StartRecording
? 'red'
@@ -44,7 +44,11 @@ export const RecordingStatusButton: React.FC<
? 'Stop Recording'
: 'Start Recording'
}
/>
>
{recordingStatus === RecordStatusEnum.StartRecording
? 'Recording'
: 'Paused'}
</Button>
</div>
);
};
26 changes: 18 additions & 8 deletions crates/lynx-proxy/src/routes/network/components/Sequence/index.tsx
Original file line number Diff line number Diff line change
@@ -2,18 +2,28 @@ import { Splitter } from 'antd';
import React from 'react';
import { Detail } from '../Detail';
import { RequestTable } from '../RequestTable';
import { ShowTypeSegmented } from '../ShowTypeSegmented';
import { Toolbar } from '../Toolbar';

interface ISequenceProps {}

export const Sequence: React.FC<ISequenceProps> = () => {
return (
<Splitter className="h-full bg-white" layout="vertical">
<Splitter.Panel defaultSize="50%" min="10%" max="90%">
<RequestTable />
</Splitter.Panel>
<Splitter.Panel defaultSize="50%" min="10%" max="90%">
<Detail />
</Splitter.Panel>
</Splitter>
<div className="flex-1 flex flex-col h-full w-full animate-fade-in">
<div className="flex items-center">
<ShowTypeSegmented />
<Toolbar />
</div>
<div className="flex-1">
<Splitter className="h-full bg-white" layout="vertical">
<Splitter.Panel defaultSize="50%" min="10%" max="90%">
<RequestTable />
</Splitter.Panel>
<Splitter.Panel defaultSize="50%" min="10%" max="90%">
<Detail />
</Splitter.Panel>
</Splitter>
</div>
</div>
);
};
Loading