mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-27 16:09:58 -07:00
wip
This commit is contained in:
@@ -1,13 +1,7 @@
|
||||
import { parse_ndjson, type NewlineDeliminatedJson } from "./ndjson";
|
||||
import { req } from "./utils";
|
||||
|
||||
export type AnalysisReport =
|
||||
| LoadingReport
|
||||
| FinishedReport;
|
||||
|
||||
export type LoadingReport = {};
|
||||
|
||||
export type FinishedReport = {
|
||||
export type AnalysisReport = {
|
||||
metadata: ReportMetadata;
|
||||
rows: AnalysisRow[];
|
||||
};
|
||||
@@ -54,7 +48,7 @@ export type InformationalEvent = {
|
||||
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 rows: AnalysisRow[] = report_json.slice(1).map((row_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}`));
|
||||
return parse_finished_report(report_json);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import { get_report, type AnalysisReport } from "./analysis";
|
||||
import type { Manifest, ManifestEntry } from "./manifest";
|
||||
import { req } from "./utils";
|
||||
|
||||
export enum AnalysisStatus {
|
||||
// rayhunter is currently analyzing this entry (note that this is distinct
|
||||
// from the currently-recording entry)
|
||||
Running,
|
||||
// this entry is queued to be analyzed
|
||||
Queued,
|
||||
// analysis is finished, and the new report can be accessed
|
||||
Finished,
|
||||
}
|
||||
|
||||
@@ -19,27 +24,40 @@ export type AnalysisResult = {
|
||||
};
|
||||
|
||||
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) {
|
||||
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() {
|
||||
this.analysis_status.clear();
|
||||
|
||||
const status: AnalysisStatusJson = JSON.parse(await req('GET', '/api/analysis'));
|
||||
if (status.running) {
|
||||
this.analysis_status.set(status.running, AnalysisStatus.Running);
|
||||
this.status.set(status.running, AnalysisStatus.Running);
|
||||
}
|
||||
|
||||
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) {
|
||||
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}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
40
bin/web/src/lib/components/AnalysisStatus.svelte
Normal file
40
bin/web/src/lib/components/AnalysisStatus.svelte
Normal file
@@ -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">
|
||||
import { ManifestEntry } from "$lib/manifest";
|
||||
import DownloadLink from '$lib/components/DownloadLink.svelte';
|
||||
import AnalysisStatus from "./AnalysisStatus.svelte";
|
||||
let { entry, current }: {
|
||||
entry: ManifestEntry;
|
||||
current: boolean;
|
||||
@@ -16,7 +17,7 @@
|
||||
<td>{entry.qmdl_size_bytes}</td>
|
||||
<td><DownloadLink url={entry.getPcapUrl()} text="pcap" /></td>
|
||||
<td><DownloadLink url={entry.getQmdlUrl()} text="qmdl" /></td>
|
||||
<td>N/A</td>
|
||||
<td><AnalysisStatus entry={entry} /></td>
|
||||
</tr>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { get_report, type AnalysisReport } from "./analysis";
|
||||
import { AnalysisStatus, type AnalysisManager } from "./analysisManager";
|
||||
|
||||
interface JsonManifest {
|
||||
entries: JsonManifestEntry[];
|
||||
current_entry: JsonManifestEntry | null;
|
||||
@@ -26,14 +29,35 @@ export class Manifest {
|
||||
// sort entries in reverse chronological order
|
||||
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 {
|
||||
public name: string;
|
||||
public start_time: Date;
|
||||
public last_message_time: Date | undefined;
|
||||
public last_message_time: Date | undefined = undefined;
|
||||
public qmdl_size_bytes: number;
|
||||
public analysis_size_bytes: number;
|
||||
public analysis_status: AnalysisStatus | undefined = undefined;
|
||||
public analysis_report: AnalysisReport | string | undefined = undefined;
|
||||
|
||||
constructor(json: JsonManifestEntry) {
|
||||
this.name = json.name;
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
export type NewlineDeliminatedJson = any[];
|
||||
|
||||
export function parse_ndjson(input: string): NewlineDeliminatedJson {
|
||||
console.log(input)
|
||||
const lines = input.split('\n');
|
||||
const result = [];
|
||||
let current_line = '';
|
||||
while (lines.length > 0) {
|
||||
current_line += lines.shift();
|
||||
if (current_line.length === 0) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const entry = JSON.parse(current_line);
|
||||
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
|
||||
// were given invalid nd-json
|
||||
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}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user