mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-07-30 18:58:11 -07:00
rm old frontend code, add favicon
This commit is contained in:
+3
-2
@@ -53,7 +53,7 @@ pub async fn get_qmdl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bundles the server's static files (html/css/js) into the binary for easy distribution
|
// Bundles the server's static files (html/css/js) into the binary for easy distribution
|
||||||
static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/static");
|
static STATIC_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/web/build");
|
||||||
|
|
||||||
pub async fn serve_static(
|
pub async fn serve_static(
|
||||||
State(state): State<Arc<ServerState>>,
|
State(state): State<Arc<ServerState>>,
|
||||||
@@ -67,7 +67,8 @@ pub async fn serve_static(
|
|||||||
if state.debug_mode {
|
if state.debug_mode {
|
||||||
let mut build_path = std::path::PathBuf::new();
|
let mut build_path = std::path::PathBuf::new();
|
||||||
build_path.push("bin");
|
build_path.push("bin");
|
||||||
build_path.push("static");
|
build_path.push("web");
|
||||||
|
build_path.push("build");
|
||||||
for part in path.split("/") {
|
for part in path.split("/") {
|
||||||
build_path.push(part);
|
build_path.push(part);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
td,
|
|
||||||
th {
|
|
||||||
border: 1px solid rgb(190, 190, 190);
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
td {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr:nth-child(even) {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
th[scope='col'] {
|
|
||||||
background-color: #696969;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
th[scope='row'] {
|
|
||||||
background-color: #d7d9f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.current {
|
|
||||||
background-color: #53fe7b;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.warning {
|
|
||||||
background-color: #fe537b;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
caption {
|
|
||||||
padding: 10px;
|
|
||||||
caption-side: bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
border-collapse: collapse;
|
|
||||||
border: 2px solid rgb(200, 200, 200);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB |
@@ -1,46 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>rayhunter</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
|
||||||
<script src="js/main.js"></script>
|
|
||||||
<script>
|
|
||||||
async function repeatedlyPopulate() {
|
|
||||||
await populateDivs();
|
|
||||||
setTimeout(repeatedlyPopulate, 1000);
|
|
||||||
}
|
|
||||||
window.onload = function() {
|
|
||||||
repeatedlyPopulate();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div>
|
|
||||||
<button onclick="startRecording()">Start Recording</button>
|
|
||||||
<button onclick="stopRecording()">Stop Recording</button>
|
|
||||||
<button onclick="deleteAllRecodings()">Delete All Recordings</button>
|
|
||||||
</div>
|
|
||||||
<table id="qmdl-manifest-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th scope="col">Date Started</th>
|
|
||||||
<th scope="col">Date of Last Message</th>
|
|
||||||
<th scope="col">Size (bytes)</th>
|
|
||||||
<th scope="col">PCAP</th>
|
|
||||||
<th scope="col">QMDL</th>
|
|
||||||
<th scope="col">Analysis Result</th>
|
|
||||||
<th scope="col">Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
</table>
|
|
||||||
<div>
|
|
||||||
<h3>Live System stats</h3>
|
|
||||||
<pre id="system-stats">Loading...</pre>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<h3>Analysis Report of Current Capture</h3>
|
|
||||||
<pre id="analysis-report">Loading...</pre>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
const STATUS_RUNNING = 'running';
|
|
||||||
const STATUS_QUEUED = 'queued';
|
|
||||||
const STATUS_NEEDS_UPDATE = 'needs-update';
|
|
||||||
const STATUS_COMPLETE = 'complete';
|
|
||||||
|
|
||||||
async function populateDivs() {
|
|
||||||
const systemStats = await getSystemStats();
|
|
||||||
const systemStatsDiv = document.getElementById('system-stats');
|
|
||||||
systemStatsDiv.innerHTML = JSON.stringify(systemStats, null, 2);
|
|
||||||
|
|
||||||
const analysisReportDiv = document.getElementById('analysis-report');
|
|
||||||
try {
|
|
||||||
const analysisReport = await getAnalysisReport('live');
|
|
||||||
analysisReportDiv.innerHTML = JSON.stringify(analysisReport, null, 2);
|
|
||||||
} catch (e) {
|
|
||||||
analysisReportDiv.innerHTML = e.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const qmdlManifest = await getQmdlManifest();
|
|
||||||
await updateAnalysisStatus(qmdlManifest);
|
|
||||||
await updateAnalysisResults(qmdlManifest);
|
|
||||||
updateQmdlManifestTable(qmdlManifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStatus(qmdlManifest, name, status) {
|
|
||||||
// ignore qmdlManifest.current_entry, it's always running
|
|
||||||
for (const entry of qmdlManifest.entries) {
|
|
||||||
if (entry.name === name) {
|
|
||||||
entry['status'] = status;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateAnalysisStatus(qmdlManifest) {
|
|
||||||
const status = JSON.parse(await req('GET', '/api/analysis'));
|
|
||||||
if (status.running) {
|
|
||||||
setStatus(qmdlManifest, status.running, STATUS_RUNNING);
|
|
||||||
}
|
|
||||||
for (const queued in status.queued) {
|
|
||||||
setStatus(qmdlManifest, queued, STATUS_QUEUED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNewlineDelimitedJSON(inputStr) {
|
|
||||||
const lines = inputStr.split('\n');
|
|
||||||
const result = [];
|
|
||||||
let currentLine = '';
|
|
||||||
while (lines.length > 0) {
|
|
||||||
currentLine += lines.shift();
|
|
||||||
try {
|
|
||||||
const entry = JSON.parse(currentLine);
|
|
||||||
result.push(entry);
|
|
||||||
currentLine = '';
|
|
||||||
// if this chunk wasn't valid JSON, there was an escaped newline in the
|
|
||||||
// JSON line, so simply continue to the next one
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateEntryAnalysisResult(entry) {
|
|
||||||
entry.analysis = {
|
|
||||||
warnings: [],
|
|
||||||
};
|
|
||||||
const report = parseNewlineDelimitedJSON(await req('GET', `/api/analysis-report/${entry.name}`));
|
|
||||||
for (const row of report) {
|
|
||||||
if (row["analysis"]) {
|
|
||||||
const timestamp = new Date(row["timestamp"]);
|
|
||||||
const analysis = row["analysis"];
|
|
||||||
for (const warning of analysis) {
|
|
||||||
entry.analysis.warnings.push({
|
|
||||||
timestamp,
|
|
||||||
warning,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entry.analysis.warnings.length === 0) {
|
|
||||||
entry.analysis_result = `0 warnings!`;
|
|
||||||
} else {
|
|
||||||
entry.analysis_result = `!!! ${entry.analysis.warnings.length} warnings !!!`;
|
|
||||||
for (const warning of entry.analysis.warnings) {
|
|
||||||
for (const event of warning.warning.events) {
|
|
||||||
if (event === null) continue;
|
|
||||||
msg = `${warning.timestamp}: ${event.message}`
|
|
||||||
entry.analysis_result += `<br>${msg}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateAnalysisResults(qmdlManifest) {
|
|
||||||
if (qmdlManifest.current_entry) {
|
|
||||||
await updateEntryAnalysisResult(qmdlManifest.current_entry);
|
|
||||||
}
|
|
||||||
for (const entry of qmdlManifest.entries) {
|
|
||||||
if (entry.status === STATUS_NEEDS_UPDATE) {
|
|
||||||
await updateEntryAnalysisResult(entry);
|
|
||||||
entry.status = STATUS_COMPLETE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateQmdlManifestTable(manifest) {
|
|
||||||
const table = document.getElementById('qmdl-manifest-table');
|
|
||||||
const numRows = table.rows.length;
|
|
||||||
for (let i=1; i<numRows; i++) {
|
|
||||||
table.deleteRow(1);
|
|
||||||
}
|
|
||||||
if (manifest.current_entry) {
|
|
||||||
const row = createEntryRow(manifest.current_entry, true);
|
|
||||||
row.classList.add('current');
|
|
||||||
table.appendChild(row)
|
|
||||||
}
|
|
||||||
for (let entry of manifest.entries) {
|
|
||||||
table.appendChild(createEntryRow(entry), false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLink(uri, text) {
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = uri;
|
|
||||||
link.innerText = text;
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createButton(uri, text) {
|
|
||||||
const link = document.createElement('button');
|
|
||||||
link.innerText = text;
|
|
||||||
link.onclick = async () => {
|
|
||||||
await req('POST', uri);
|
|
||||||
populateDivs();
|
|
||||||
};
|
|
||||||
return link;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createEntryRow(entry, isCurrent) {
|
|
||||||
const row = document.createElement('tr');
|
|
||||||
const name = document.createElement('th');
|
|
||||||
name.scope = 'row';
|
|
||||||
name.innerText = entry.name;
|
|
||||||
row.appendChild(name);
|
|
||||||
|
|
||||||
for (const key of ['start_time', 'last_message_time', 'qmdl_size_bytes']) {
|
|
||||||
const td = document.createElement('td');
|
|
||||||
td.innerText = entry[key];
|
|
||||||
row.appendChild(td);
|
|
||||||
}
|
|
||||||
|
|
||||||
const pcapTd = document.createElement('td');
|
|
||||||
pcapTd.appendChild(createLink(`/api/pcap/${entry.name}`, 'pcap'));
|
|
||||||
row.appendChild(pcapTd);
|
|
||||||
|
|
||||||
const qmdlTd = document.createElement('td');
|
|
||||||
qmdlTd.appendChild(createLink(`/api/qmdl/${entry.name}.qmdl`, 'qmdl'));
|
|
||||||
row.appendChild(qmdlTd);
|
|
||||||
|
|
||||||
const analysisResult = document.createElement('td');
|
|
||||||
analysisResult.innerHTML = entry.analysis_result;
|
|
||||||
if (entry.analysis.warnings.length > 0) {
|
|
||||||
row.classList.add("warning");
|
|
||||||
}
|
|
||||||
row.appendChild(analysisResult);
|
|
||||||
|
|
||||||
const actionsButtons = document.createElement('td');
|
|
||||||
actionsButtons.appendChild(createButton(`/api/delete-recording/${entry.name}`, 'Delete'));
|
|
||||||
row.appendChild(actionsButtons);
|
|
||||||
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getAnalysisReport(name) {
|
|
||||||
const rows = await req('GET', `/api/analysis-report/${name}`);
|
|
||||||
return rows.split('\n')
|
|
||||||
.filter(row => row.length > 0)
|
|
||||||
.map(row => JSON.parse(row));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSystemStats() {
|
|
||||||
return JSON.parse(await req('GET', '/api/system-stats'));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getQmdlManifest() {
|
|
||||||
const manifest = JSON.parse(await req('GET', '/api/qmdl-manifest'));
|
|
||||||
if (manifest.current_entry) {
|
|
||||||
parseQmdlEntry(manifest.current_entry);
|
|
||||||
}
|
|
||||||
for (entry of manifest.entries) {
|
|
||||||
parseQmdlEntry(entry);
|
|
||||||
}
|
|
||||||
// sort them in reverse chronological order
|
|
||||||
manifest.entries.reverse();
|
|
||||||
return manifest;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseQmdlEntry(entry) {
|
|
||||||
entry.status = STATUS_NEEDS_UPDATE;
|
|
||||||
entry.analysis_result = 'Waiting...';
|
|
||||||
entry.start_time = new Date(entry.start_time);
|
|
||||||
if (entry.last_message_time === null) {
|
|
||||||
entry.last_message_time = "N/A";
|
|
||||||
} else {
|
|
||||||
entry.last_message_time = new Date(entry.last_message_time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function startRecording() {
|
|
||||||
await req('POST', '/api/start-recording');
|
|
||||||
populateDivs();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function stopRecording() {
|
|
||||||
await req('POST', '/api/stop-recording');
|
|
||||||
populateDivs();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteAllRecodings() {
|
|
||||||
if (window.confirm("Are you sure you want to permanently delete all of your recordings?")) {
|
|
||||||
await req('POST', '/api/delete-all-recordings');
|
|
||||||
populateDivs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function req(method, url) {
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: method,
|
|
||||||
});
|
|
||||||
const body = await response.text();
|
|
||||||
if (response.status >= 200 && response.status < 300) {
|
|
||||||
return body;
|
|
||||||
} else {
|
|
||||||
throw new Error(body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.2 KiB |
Reference in New Issue
Block a user