better controls, formatting, etc

This commit is contained in:
Will Greenberg
2025-04-14 20:05:00 -07:00
parent c4b2c3bbe2
commit a33c7511eb
11 changed files with 133 additions and 31 deletions

View File

@@ -2,13 +2,12 @@
import { AnalysisStatus } from "$lib/analysisManager.svelte";
import { EventType } from "$lib/analysis.svelte";
import type { ManifestEntry } from "$lib/manifest.svelte";
let { entry, analysis_status }: {
let { entry }: {
entry: ManifestEntry,
analysis_status: AnalysisStatus | undefined,
} = $props();
let summary = $derived.by(() => {
if (analysis_status === AnalysisStatus.Queued) {
if (entry.analysis_status === AnalysisStatus.Queued) {
return 'Queued...';
} else if (entry.analysis_status === AnalysisStatus.Running) {
return 'Running...';

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import { req } from "$lib/utils.svelte";
import DeleteButton from "./DeleteButton.svelte";
import RecordingControls from "./RecordingControls.svelte";
let { server_is_recording }: {
server_is_recording: boolean;
} = $props();
function confirmDelete() {
if (window.confirm(`Permanently delete ALL entries?`)) {
req('POST', '/api/delete-all-recordings')
}
}
</script>
<div class="flex flex-row gap-2">
<RecordingControls {server_is_recording} />
<DeleteButton
text="Delete ALL Entries"
prompt={`Are you sure you want to delete ALL entries?`}
url={`/api/delete-all-recordings`}
/>
</div>

View File

@@ -0,0 +1,28 @@
<script lang="ts">
import { ManifestEntry } from "$lib/manifest.svelte";
import { req } from "$lib/utils.svelte";
let { text, url, prompt }: {
text?: string,
url: string,
prompt: string,
} = $props();
function confirmDelete() {
if (window.confirm(prompt)) {
req('POST', url)
}
}
</script>
<button class="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md flex flex-row" onclick={confirmDelete} aria-label="delete">
<p>{text}</p>
<svg
style="width:24px;height:24px"
viewBox="0 0 24 24"
>
<path
fill="hsl(200, 40%, 20%)"
d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"
/>
</svg>
</button>

View File

@@ -3,12 +3,14 @@
url: string;
text: string;
} = $props();
function download() {
window.location.href = url;
}
</script>
<a href={url}>📥 {text}</a>
<style>
a {
@apply underline text-blue-400;
}
</style>
<button class="text-blue-400 flex flex-row underline" onclick={download}>
{text} <svg class="fill-current w-4 h-4 m-1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M13 8V2H7v6H2l8 8 8-8h-5zM0 18h20v2H0v-2z"/>
</svg>
</button>

View File

@@ -10,7 +10,7 @@
<table class="table-auto border">
<thead class="p-2">
<tr class="bg-gray-300 p-2 m-2">
<tr class="bg-gray-300">
<th scope="col">Name</th>
<th scope="col">Date Started</th>
<th scope="col">Date of Last Message</th>
@@ -18,6 +18,7 @@
<th scope="col">PCAP</th>
<th scope="col">QMDL</th>
<th scope="col">Analysis Result</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>

View File

@@ -1,23 +1,37 @@
<script lang="ts">
import { ManifestEntry } from "$lib/manifest.svelte";
import DownloadLink from '$lib/components/DownloadLink.svelte';
import DeleteButton from "$lib/components/DeleteButton.svelte";
import AnalysisStatus from "./AnalysisStatus.svelte";
let { entry, current }: {
entry: ManifestEntry;
current: boolean;
} = $props();
// bg-gray-100
// bg-green-300
// passing `undefined` as the locale uses the browser default
const date_formatter = new Intl.DateTimeFormat(undefined, {
timeStyle: "long",
dateStyle: "short",
});
let row_color = current ? "bg-green-300" : "even:bg-gray-100";
</script>
<tr class="{row_color} border-b">
<th class="font-bold p-2 border-b bg-blue-100" scope='row'>{entry.name}</th>
<td class="p-2">{entry.start_time}</td>
<td class="p-2">{entry.last_message_time}</td>
<th class="font-bold p-2 bg-blue-100" scope='row'>{entry.name}</th>
<td class="p-2">{date_formatter.format(entry.start_time)}</td>
<td class="p-2">{date_formatter.format(entry.last_message_time)}</td>
<td class="p-2">{entry.qmdl_size_bytes}</td>
<td class="p-2"><DownloadLink url={entry.getPcapUrl()} text="pcap" /></td>
<td class="p-2"><DownloadLink url={entry.getQmdlUrl()} text="qmdl" /></td>
<td class="p-2"><AnalysisStatus analysis_status={entry.analysis_status} entry={entry} /></td>
<td class="p-2"><AnalysisStatus entry={entry} /></td>
{#if current}
<td class="p-2"></td>
{:else}
<td class="p-2">
<DeleteButton
prompt={`Are you sure you want to delete entry ${entry.name}?`}
url={entry.getDeleteUrl()}
/>
</td>
{/if}
</tr>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { req } from "$lib/utils.svelte";
let { server_is_recording: currently_recording }: {
let { server_is_recording }: {
server_is_recording: boolean;
} = $props();
let client_set_recording = $state(currently_recording);
let waiting_for_server = $derived(client_set_recording !== currently_recording);
let client_set_recording = $state(server_is_recording);
let waiting_for_server = $derived(client_set_recording !== server_is_recording);
async function start_recording() {
await req('POST', '/api/start-recording');
@@ -17,16 +17,16 @@
client_set_recording = false;
}
const stop_recording_classes = "bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-full";
const start_recording_classes = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full";
const stop_recording_classes = "bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-md";
const start_recording_classes = "bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-md";
</script>
<div>
{#if waiting_for_server}
<button class={currently_recording ? stop_recording_classes : start_recording_classes}>
{currently_recording ? "Stopping..." : "Starting..."}
<button class={server_is_recording ? stop_recording_classes : start_recording_classes}>
{server_is_recording ? "Stopping..." : "Starting..."}
</button>
{:else if currently_recording}
{:else if server_is_recording}
<button class={stop_recording_classes} onclick={stop_recording}>Stop Recording</button>
{:else}
<button class={start_recording_classes} onclick={start_recording}>Start Recording</button>

View File

@@ -0,0 +1,31 @@
<script lang="ts">
import { type SystemStats } from "$lib/systemStats";
let { stats }: {
stats: SystemStats;
} = $props();
</script>
<div>
<p class="text-xl">System Stats</p>
<table class="table-auto border">
<tbody>
<tr class="border">
<th class="border">
Storage
</th>
<td class="border">
{stats.disk_stats.used_percent} used ({stats.disk_stats.used_size} / {stats.disk_stats.available_size})
</td>
</tr>
<tr class="border-b">
<th class="border">
Memory (RAM)
</th>
<td class="border">
Free: {stats.memory_stats.free}, Used: {stats.memory_stats.used}
</td>
</tr>
</tbody>
</table>
</div>

View File

@@ -80,4 +80,8 @@ export class ManifestEntry {
getAnalysisReportUrl(): string {
return `/api/analysis-report/${this.name}`;
}
getDeleteUrl(): string {
return `/api/delete-recording/${this.name}`;
}
}

View File

@@ -2,11 +2,10 @@
import { Manifest, ManifestEntry } from "$lib/manifest.svelte";
import { get_manifest, get_system_stats } from "$lib/utils.svelte";
import ManifestTable from "$lib/components/ManifestTable.svelte";
import { onMount } from "svelte";
import type { SystemStats } from "$lib/systemStats";
import { AnalysisManager } from "$lib/analysisManager.svelte";
import { writable, readable, type Readable, type Writable } from "svelte/store";
import RecordingControls from "$lib/components/RecordingControls.svelte";
import SystemStatsTable from "$lib/components/SystemStatsTable.svelte";
import ControlBar from "$lib/components/ControlBar.svelte";
let manager: AnalysisManager = new AnalysisManager();
let loaded = $state(false);
@@ -16,7 +15,6 @@
let system_stats: SystemStats | undefined = $state(undefined);
$effect(() => {
const interval = setInterval(async () => {
loaded = true;
await manager.update();
let new_manifest = await get_manifest();
await new_manifest.set_analysis_status(manager);
@@ -25,16 +23,18 @@
recording = current_entry !== undefined;
system_stats = await get_system_stats();
loaded = true;
}, 3000);
return () => clearInterval(interval);
})
</script>
<div class="p-8">
<div class="p-8 flex flex-col gap-2">
{#if loaded}
<RecordingControls server_is_recording={recording} />
<ControlBar server_is_recording={recording} />
<ManifestTable entries={entries} current_entry={current_entry} />
<SystemStatsTable stats={system_stats!} />
{:else}
<p>Loading...</p>
{/if}