mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-07-28 01:48:09 -07:00
wip
This commit is contained in:
@@ -1,13 +1,7 @@
|
|||||||
import { parse_ndjson, type NewlineDeliminatedJson } from "./ndjson";
|
import { parse_ndjson, type NewlineDeliminatedJson } from "./ndjson";
|
||||||
import { req } from "./utils";
|
import { req } from "./utils";
|
||||||
|
|
||||||
export type AnalysisReport =
|
export type AnalysisReport = {
|
||||||
| LoadingReport
|
|
||||||
| FinishedReport;
|
|
||||||
|
|
||||||
export type LoadingReport = {};
|
|
||||||
|
|
||||||
export type FinishedReport = {
|
|
||||||
metadata: ReportMetadata;
|
metadata: ReportMetadata;
|
||||||
rows: AnalysisRow[];
|
rows: AnalysisRow[];
|
||||||
};
|
};
|
||||||
@@ -54,7 +48,7 @@ export type InformationalEvent = {
|
|||||||
message: string;
|
message: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function parse_finished_report(report_json: NewlineDeliminatedJson): FinishedReport {
|
export function parse_finished_report(report_json: NewlineDeliminatedJson): AnalysisReport {
|
||||||
const metadata: ReportMetadata = report_json[0]; // this can be cast directly
|
const metadata: ReportMetadata = report_json[0]; // this can be cast directly
|
||||||
const rows: AnalysisRow[] = report_json.slice(1).map((row_json: any) => {
|
const rows: AnalysisRow[] = report_json.slice(1).map((row_json: any) => {
|
||||||
const analysis: PacketAnalysis[] = row_json.analysis.map((analysis_json: any) => {
|
const analysis: PacketAnalysis[] = row_json.analysis.map((analysis_json: any) => {
|
||||||
@@ -93,7 +87,7 @@ export function parse_finished_report(report_json: NewlineDeliminatedJson): Fini
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get_report(name: string): Promise<FinishedReport> {
|
export async function get_report(name: string): Promise<AnalysisReport> {
|
||||||
const report_json = parse_ndjson(await req('GET', `/api/analysis-report/${name}`));
|
const report_json = parse_ndjson(await req('GET', `/api/analysis-report/${name}`));
|
||||||
return parse_finished_report(report_json);
|
return parse_finished_report(report_json);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
|
import { get_report, type AnalysisReport } from "./analysis";
|
||||||
import type { Manifest, ManifestEntry } from "./manifest";
|
import type { Manifest, ManifestEntry } from "./manifest";
|
||||||
import { req } from "./utils";
|
import { req } from "./utils";
|
||||||
|
|
||||||
export enum AnalysisStatus {
|
export enum AnalysisStatus {
|
||||||
|
// rayhunter is currently analyzing this entry (note that this is distinct
|
||||||
|
// from the currently-recording entry)
|
||||||
Running,
|
Running,
|
||||||
|
// this entry is queued to be analyzed
|
||||||
Queued,
|
Queued,
|
||||||
|
// analysis is finished, and the new report can be accessed
|
||||||
Finished,
|
Finished,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,27 +24,40 @@ export type AnalysisResult = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class AnalysisManager {
|
export class AnalysisManager {
|
||||||
public analysis_status: Map<string, AnalysisStatus> = new Map();
|
public status: Map<string, AnalysisStatus> = new Map();
|
||||||
|
public reports: Map<string, AnalysisReport | string> = new Map();
|
||||||
|
|
||||||
public async run_analysis(name: string) {
|
public async run_analysis(name: string) {
|
||||||
await req('POST', `/api/analysis/${name}`);
|
await req('POST', `/api/analysis/${name}`);
|
||||||
this.analysis_status.set(name, AnalysisStatus.Queued);
|
this.status.set(name, AnalysisStatus.Queued);
|
||||||
|
this.reports.delete(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async update() {
|
public async update() {
|
||||||
this.analysis_status.clear();
|
|
||||||
|
|
||||||
const status: AnalysisStatusJson = JSON.parse(await req('GET', '/api/analysis'));
|
const status: AnalysisStatusJson = JSON.parse(await req('GET', '/api/analysis'));
|
||||||
if (status.running) {
|
if (status.running) {
|
||||||
this.analysis_status.set(status.running, AnalysisStatus.Running);
|
this.status.set(status.running, AnalysisStatus.Running);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of status.queued) {
|
for (const entry of status.queued) {
|
||||||
this.analysis_status.set(entry, AnalysisStatus.Queued);
|
this.status.set(entry, AnalysisStatus.Queued);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const entry of status.finished) {
|
for (const entry of status.finished) {
|
||||||
this.analysis_status.set(entry, AnalysisStatus.Finished);
|
// if entry was already finished, nothing to do
|
||||||
|
if (this.status.get(entry) === AnalysisStatus.Finished) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status.set(entry, AnalysisStatus.Finished);
|
||||||
|
|
||||||
|
// fetch the analysis report
|
||||||
|
this.reports.delete(entry);
|
||||||
|
get_report(entry).then(report => {
|
||||||
|
this.reports.set(entry, report);
|
||||||
|
}).catch(err => {
|
||||||
|
this.reports.set(entry, `Failed to get analysis: ${err}`);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { AnalysisStatus } from "$lib/analysisManager";
|
||||||
|
import { EventType } from "$lib/analysis";
|
||||||
|
import type { ManifestEntry } from "$lib/manifest";
|
||||||
|
let { entry }: {
|
||||||
|
entry: ManifestEntry,
|
||||||
|
} = $props();
|
||||||
|
|
||||||
|
let summary = $state('Loading...');
|
||||||
|
if (entry.analysis_status === AnalysisStatus.Queued) {
|
||||||
|
summary = 'Queued...';
|
||||||
|
} else if (entry.analysis_status === AnalysisStatus.Running) {
|
||||||
|
summary = 'Running...';
|
||||||
|
} else if (entry.analysis_status === AnalysisStatus.Finished) {
|
||||||
|
if (entry.analysis_report === undefined) {
|
||||||
|
summary = 'Loading...';
|
||||||
|
} else if (typeof(entry.analysis_report) === 'string') {
|
||||||
|
summary = entry.analysis_report;
|
||||||
|
} else {
|
||||||
|
let num_warnings = 0;
|
||||||
|
for (let row of entry.analysis_report.rows) {
|
||||||
|
for (let analysis of row.analysis) {
|
||||||
|
for (let event of analysis.events) {
|
||||||
|
if (event.type === EventType.Warning) {
|
||||||
|
num_warnings += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary = `${num_warnings} warnings`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{summary}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ManifestEntry } from "$lib/manifest";
|
import { ManifestEntry } from "$lib/manifest";
|
||||||
import DownloadLink from '$lib/components/DownloadLink.svelte';
|
import DownloadLink from '$lib/components/DownloadLink.svelte';
|
||||||
|
import AnalysisStatus from "./AnalysisStatus.svelte";
|
||||||
let { entry, current }: {
|
let { entry, current }: {
|
||||||
entry: ManifestEntry;
|
entry: ManifestEntry;
|
||||||
current: boolean;
|
current: boolean;
|
||||||
@@ -16,7 +17,7 @@
|
|||||||
<td>{entry.qmdl_size_bytes}</td>
|
<td>{entry.qmdl_size_bytes}</td>
|
||||||
<td><DownloadLink url={entry.getPcapUrl()} text="pcap" /></td>
|
<td><DownloadLink url={entry.getPcapUrl()} text="pcap" /></td>
|
||||||
<td><DownloadLink url={entry.getQmdlUrl()} text="qmdl" /></td>
|
<td><DownloadLink url={entry.getQmdlUrl()} text="qmdl" /></td>
|
||||||
<td>N/A</td>
|
<td><AnalysisStatus entry={entry} /></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import { get_report, type AnalysisReport } from "./analysis";
|
||||||
|
import { AnalysisStatus, type AnalysisManager } from "./analysisManager";
|
||||||
|
|
||||||
interface JsonManifest {
|
interface JsonManifest {
|
||||||
entries: JsonManifestEntry[];
|
entries: JsonManifestEntry[];
|
||||||
current_entry: JsonManifestEntry | null;
|
current_entry: JsonManifestEntry | null;
|
||||||
@@ -26,14 +29,35 @@ export class Manifest {
|
|||||||
// sort entries in reverse chronological order
|
// sort entries in reverse chronological order
|
||||||
this.entries.reverse();
|
this.entries.reverse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async set_analysis_status(manager: AnalysisManager) {
|
||||||
|
for (let entry of this.entries) {
|
||||||
|
entry.analysis_status = manager.status.get(entry.name);
|
||||||
|
entry.analysis_report = manager.reports.get(entry.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.current_entry) {
|
||||||
|
try {
|
||||||
|
this.current_entry.analysis_report = await get_report(this.current_entry.name);
|
||||||
|
} catch(err) {
|
||||||
|
this.current_entry.analysis_report = `Err: failed to get analysis report: ${err}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// the current entry should always be considered "finished", as its
|
||||||
|
// analysis report is always available
|
||||||
|
this.current_entry.analysis_status = AnalysisStatus.Finished;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ManifestEntry {
|
export class ManifestEntry {
|
||||||
public name: string;
|
public name: string;
|
||||||
public start_time: Date;
|
public start_time: Date;
|
||||||
public last_message_time: Date | undefined;
|
public last_message_time: Date | undefined = undefined;
|
||||||
public qmdl_size_bytes: number;
|
public qmdl_size_bytes: number;
|
||||||
public analysis_size_bytes: number;
|
public analysis_size_bytes: number;
|
||||||
|
public analysis_status: AnalysisStatus | undefined = undefined;
|
||||||
|
public analysis_report: AnalysisReport | string | undefined = undefined;
|
||||||
|
|
||||||
constructor(json: JsonManifestEntry) {
|
constructor(json: JsonManifestEntry) {
|
||||||
this.name = json.name;
|
this.name = json.name;
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
export type NewlineDeliminatedJson = any[];
|
export type NewlineDeliminatedJson = any[];
|
||||||
|
|
||||||
export function parse_ndjson(input: string): NewlineDeliminatedJson {
|
export function parse_ndjson(input: string): NewlineDeliminatedJson {
|
||||||
|
console.log(input)
|
||||||
const lines = input.split('\n');
|
const lines = input.split('\n');
|
||||||
const result = [];
|
const result = [];
|
||||||
let current_line = '';
|
let current_line = '';
|
||||||
while (lines.length > 0) {
|
while (lines.length > 0) {
|
||||||
current_line += lines.shift();
|
current_line += lines.shift();
|
||||||
|
if (current_line.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const entry = JSON.parse(current_line);
|
const entry = JSON.parse(current_line);
|
||||||
result.push(entry);
|
result.push(entry);
|
||||||
@@ -16,7 +20,7 @@ export function parse_ndjson(input: string): NewlineDeliminatedJson {
|
|||||||
// however, if we've reached the end of the input, that means we
|
// however, if we've reached the end of the input, that means we
|
||||||
// were given invalid nd-json
|
// were given invalid nd-json
|
||||||
if (lines.length === 0) {
|
if (lines.length === 0) {
|
||||||
throw new Error(`unable to parse invalid nd-json: ${e}`);
|
throw new Error(`unable to parse invalid nd-json: ${e}, "${current_line}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,30 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Manifest, ManifestEntry } from "$lib/manifest";
|
import { Manifest, ManifestEntry } from "$lib/manifest";
|
||||||
import { get_manifest, get_system_stats } from "$lib/utils";
|
import { get_manifest, get_system_stats } from "$lib/utils";
|
||||||
import ManifestTable from "$lib/components/ManifestTable.svelte";
|
import ManifestTable from "$lib/components/ManifestTable.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import type { SystemStats } from "$lib/systemStats";
|
import type { SystemStats } from "$lib/systemStats";
|
||||||
import { AnalysisManager } from "$lib/analysisManager";
|
import { AnalysisManager } from "$lib/analysisManager";
|
||||||
|
|
||||||
let manifest: Manifest | undefined = $state(undefined);
|
let manifest: Manifest | undefined = $state(undefined);
|
||||||
let system_stats: SystemStats | undefined = $state(undefined);
|
let system_stats: SystemStats | undefined = $state(undefined);
|
||||||
let manager: AnalysisManager = new AnalysisManager();
|
let manager: AnalysisManager = new AnalysisManager();
|
||||||
let analysis_status = $state([]);
|
let analysis_status = $state([]);
|
||||||
async function update(): Promise<void> {
|
|
||||||
manifest = await get_manifest();
|
|
||||||
system_stats = await get_system_stats();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
async function update(): Promise<void> {
|
||||||
const interval = setInterval(() => {
|
await manager.update();
|
||||||
update();
|
manifest = await get_manifest();
|
||||||
}, 1000);
|
manifest.set_analysis_status(manager);
|
||||||
|
system_stats = await get_system_stats();
|
||||||
|
}
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
onMount(() => {
|
||||||
});
|
const interval = setInterval(() => {
|
||||||
|
update();
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-8">
|
<div class="p-8">
|
||||||
|
|||||||
Reference in New Issue
Block a user