Skip to content

Commit

Permalink
Merge pull request #39 from OpenAttackDefenseTools/teameurope/fronten…
Browse files Browse the repository at this point in the history
…d-features

Merge frontend changes from team-europe/tulip
  • Loading branch information
RickdeJager committed May 3, 2024
2 parents 918a344 + 60b1281 commit 8a975ce
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 66 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,6 @@ venv.bak/
.mypy_cache/

workspace.xml

.idea

/traffic
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"react-apexcharts": "^1.4.0",
"react-diff-viewer": "^3.1.1",
"react-dom": "^18.0.0",
"react-hotkeys-hook": "^4.4.1",
"react-redux": "^8.0.2",
"react-router-dom": "^6.3.0",
"react-virtuoso": "^2.12.1"
Expand Down
13 changes: 13 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BrowserRouter, Routes, Route, Outlet } from "react-router-dom";
import { Suspense } from "react";
import { useHotkeys } from 'react-hotkeys-hook';

import "./App.css";
import { Header } from "./components/Header";
Expand All @@ -10,6 +11,7 @@ import { DiffView } from "./pages/DiffView";
import { Corrie } from "./components/Corrie";

function App() {
useHotkeys('esc', () => (document.activeElement as HTMLElement).blur(), {enableOnFormTags: true});
return (
<BrowserRouter>
<Routes>
Expand Down Expand Up @@ -40,6 +42,7 @@ function App() {
}
/>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</BrowserRouter>
);
Expand All @@ -65,4 +68,14 @@ function Layout() {
</div>
);
}


function PageNotFound() {
return (
<div>
<h2>404 Page not found</h2>
</div>
);
}

export default App;
4 changes: 4 additions & 0 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export const tulipApi = createApi({
getServices: builder.query<Service[], void>({
query: () => "/services",
}),
getFlagRegex: builder.query<string, void>({
query: () => "/flag_regex",
}),
getFlow: builder.query<FullFlow, string>({
query: (id) => `/flow/${id}`,
}),
Expand Down Expand Up @@ -102,6 +105,7 @@ export const tulipApi = createApi({

export const {
useGetServicesQuery,
useGetFlagRegexQuery,
useGetFlowQuery,
useGetFlowsQuery,
useLazyGetFlowsQuery,
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/components/FlowList.module.css
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
.list_container {

@apply flex flex-col gap-2;
@apply flex flex-col;

a {
@apply pb-1;
}

li {
@apply bg-gray-100 p-2 rounded-md focus:ring-4 m-2 list-none;
@apply bg-gray-100 p-2 focus:ring-4 border-t border-gray-200 list-none;
}

li.active {
@apply ring-2 ring-gray-500
@apply border-y border-l-4 border-gray-500 bg-gray-300/50
}

}
75 changes: 70 additions & 5 deletions frontend/src/components/FlowList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
useParams,
useNavigate,
} from "react-router-dom";
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import { useHotkeys } from 'react-hotkeys-hook';
import { Flow } from "../types";
import {
SERVICE_FILTER_KEY,
Expand Down Expand Up @@ -36,6 +37,9 @@ export function FlowList() {
let [searchParams] = useSearchParams();
let params = useParams();

// we add a local variable to prevent racing with the browser location API
let openedFlowID = params.id

const { data: availableTags } = useGetTagsQuery();
const { data: services } = useGetServicesQuery();

Expand All @@ -46,8 +50,7 @@ export function FlowList() {

const [starFlow] = useStarFlowMutation();

// Set default flowIndex to Infinity, so that initialTopMostItemIndex != 0 and therefore scrolledToInitialItem != true
const [flowIndex, setFlowIndex] = useState<number>(1);
const [flowIndex, setFlowIndex] = useState<number>(0);

const virtuoso = useRef<VirtuosoHandle>(null);

Expand All @@ -60,7 +63,7 @@ export function FlowList() {

const debounced_text_filter = useDebounce(text_filter, 300);

const { data: flowData, isLoading } = useGetFlowsQuery(
const { data: flowData, isLoading, refetch } = useGetFlowsQuery(
{
"flow.data": debounced_text_filter,
dst_ip: service?.ip,
Expand Down Expand Up @@ -90,6 +93,66 @@ export function FlowList() {
await starFlow({ id: flow._id.$oid, star: !flow.tags.includes("starred") });
};

const navigate = useNavigate();

useEffect(() => {
virtuoso?.current?.scrollIntoView({
index: flowIndex,
behavior: 'auto',
done: () => {
if (transformedFlowData && transformedFlowData[flowIndex ?? 0]) {
let idAtIndex = transformedFlowData[flowIndex ?? 0]._id.$oid;
// if the current flow ID at the index indeed did change (ie because of keyboard navigation), we need to update the URL as well as local ID
if (idAtIndex !== openedFlowID) {
navigate(`/flow/${idAtIndex}?${searchParams}`)
openedFlowID = idAtIndex
}
}
},
})
},
[flowIndex]
)

// TODO: there must be a better way to do this
// this gets called on every refetch, we dont want to iterate all flows on every refetch
// so because performance, we hack this by checking if the transformedFlowData length changed
const [transformedFlowDataLength, setTransformedFlowDataLength] = useState<number>(0);
useEffect(
() => {
if (transformedFlowData && transformedFlowDataLength != transformedFlowData?.length) {
setTransformedFlowDataLength(transformedFlowData?.length)

for (let i = 0; i < transformedFlowData?.length; i++) {
if (transformedFlowData[i]._id.$oid === openedFlowID) {
if (i !== flowIndex) {
setFlowIndex(i)
}
return
}
}
setFlowIndex(0)
}
},
[transformedFlowData]
)

useHotkeys('j', () => setFlowIndex(fi => Math.min((transformedFlowData?.length ?? 1)-1, fi + 1)), [transformedFlowData?.length]);
useHotkeys('k', () => setFlowIndex(fi => Math.max(0, fi - 1)));
useHotkeys('i', () => {
setShowFilters(true)
if ((availableTags ?? []).includes("flag-in")) {
dispatch(toggleFilterTag("flag-in"))
}
}, [availableTags]);
useHotkeys('o', () => {
setShowFilters(true)
if ((availableTags ?? []).includes("flag-out")) {
dispatch(toggleFilterTag("flag-out"))
}
}, [availableTags]);
useHotkeys('r', () => refetch());

const [showFilters, setShowFilters] = useState(false);

return (
Expand Down Expand Up @@ -142,13 +205,15 @@ export function FlowList() {
itemContent={(index, flow) => (
<Link
to={`/flow/${flow._id.$oid}?${searchParams}`}
onClick={() => setFlowIndex(index)}
key={flow._id.$oid}
className="focus-visible:rounded-md"
//style={{ paddingTop: '1em' }}
>
<FlowListEntry
key={flow._id.$oid}
flow={flow}
isActive={flow._id.$oid === params.id}
isActive={flow._id.$oid === openedFlowID}
onHeartClick={onHeartHandler}
/>
</Link>
Expand Down
93 changes: 73 additions & 20 deletions frontend/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { format, parse } from "date-fns";
import { Suspense, useState } from "react";
import { useHotkeys } from 'react-hotkeys-hook';
import {
Link,
useParams,
Expand Down Expand Up @@ -67,11 +68,18 @@ function ServiceSelection() {
function TextSearch() {
const FILTER_KEY = TEXT_FILTER_KEY;
let [searchParams, setSearchParams] = useSearchParams();
useHotkeys('s', (e) => {
let el = document.getElementById('search') as HTMLInputElement;
el?.focus();
el?.select();
e.preventDefault()
});
return (
<div>
<input
type="text"
placeholder="regex"
id="search"
value={searchParams.get(FILTER_KEY) || ""}
onChange={(event) => {
let textFilter = event.target.value;
Expand Down Expand Up @@ -170,6 +178,7 @@ function StartDateSelection() {
<div>
<input
className="w-20"
id="startdateselection"
type="number"
placeholder="from"
value={startTick}
Expand All @@ -188,6 +197,7 @@ function EndDateSelection() {
<div>
<input
className="w-20"
id="enddateselection"
type="number"
placeholder="to"
value={endTick}
Expand All @@ -206,22 +216,35 @@ function FirstDiff() {
searchParams.get(FIRST_DIFF_KEY) ?? ""
);

function setFirstDiffFlow() {
let textFilter = params.id;
if (textFilter) {
searchParams.set(FIRST_DIFF_KEY, textFilter);
setFirstFlow(textFilter);
} else {
searchParams.delete(FIRST_DIFF_KEY);
setFirstFlow("");
}
setSearchParams(searchParams);
}

useHotkeys("f", () => {
setFirstDiffFlow();
});

return (
<input
type="text"
className="md:w-72"
placeholder="First Diff ID"
readOnly
value={firstFlow}
onClick={(event) => {
let textFilter = params.id;
if (textFilter) {
searchParams.set(FIRST_DIFF_KEY, textFilter);
setFirstFlow(textFilter);
} else {
searchParams.delete(FIRST_DIFF_KEY);
setFirstFlow("");
}
onClick={(event) => setFirstDiffFlow()}
onContextMenu={(event) => {
searchParams.delete(FIRST_DIFF_KEY);
setFirstFlow("");
setSearchParams(searchParams);
event.preventDefault();
}}
></input>
);
Expand All @@ -234,22 +257,35 @@ function SecondDiff() {
searchParams.get(SECOND_DIFF_KEY) ?? ""
);

function setSecondDiffFlow() {
let textFilter = params.id;
if (textFilter) {
searchParams.set(SECOND_DIFF_KEY, textFilter);
setSecondFlow(textFilter);
} else {
searchParams.delete(SECOND_DIFF_KEY);
setSecondFlow("");
}
setSearchParams(searchParams);
}

useHotkeys("g", () => {
setSecondDiffFlow();
});

return (
<input
type="text"
className="md:w-72"
placeholder="Second Flow ID"
readOnly
value={secondFlow}
onClick={(event) => {
let textFilter = params.id;
if (textFilter) {
searchParams.set(SECOND_DIFF_KEY, textFilter);
setSecondFlow(textFilter);
} else {
searchParams.delete(SECOND_DIFF_KEY);
setSecondFlow("");
}
onClick={(event) => setSecondDiffFlow()}
onContextMenu={(event) => {
searchParams.delete(SECOND_DIFF_KEY);
setSecondFlow("");
setSearchParams(searchParams);
event.preventDefault();
}}
></input>
);
Expand All @@ -261,11 +297,20 @@ function Diff() {
let [searchParams] = useSearchParams();

let navigate = useNavigate();

function navigateToDiff() {
navigate(`/diff/${params.id ?? ""}?${searchParams}`, { replace: true });
}

useHotkeys("d", () => {
navigateToDiff();
});

return (
<button
className=" bg-amber-100 text-gray-800 rounded-md px-2 py-1"
onClick={() => {
navigate(`/diff/${params.id ?? ""}?${searchParams}`, { replace: true });
navigateToDiff()
}}
>
Diff
Expand All @@ -275,7 +320,15 @@ function Diff() {

export function Header() {
let [searchParams] = useSearchParams();
const { setToLastnTicks, currentTick } = useMessyTimeStuff();
const { setToLastnTicks, currentTick, setTimeParam } = useMessyTimeStuff();

useHotkeys('a', () => setToLastnTicks(5));
useHotkeys('c', () => {
(document.getElementById("startdateselection") as HTMLInputElement).value = "";
(document.getElementById("enddateselection") as HTMLInputElement).value = "";
setTimeParam("", START_FILTER_KEY);
setTimeParam("", END_FILTER_KEY);
});

return (
<>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export const CORRELATION_MODE_KEY = "correlation";
export const SERVICE_REFETCH_INTERVAL_MS = 15000;
export const TICK_REFETCH_INTERVAL_MS = 10000;
export const FLOW_LIST_REFETCH_INTERVAL_MS = 30000;
export const MAX_LENGTH_FOR_HIGHLIGHT = 400000;

0 comments on commit 8a975ce

Please sign in to comment.