mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-04-29 08:59:59 -07:00
Add error reporting to the daemon web UI
This error reporting comes in two forms: - Errors updating the UI - Errors with user actions The former is displayed as one error until a refresh succeeds again. The latter creates an number of persistent errors until they are cleared by the user.
This commit is contained in:
24
daemon/web/src/lib/action_errors.svelte.ts
Normal file
24
daemon/web/src/lib/action_errors.svelte.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
export class ActionError extends Error {
|
||||
// The number of this an identical error has happened.
|
||||
// This is shown as a number next to the error in the UI.
|
||||
times = $state(1);
|
||||
|
||||
constructor(message: string, cause: Error) {
|
||||
super(message);
|
||||
this.cause = cause;
|
||||
}
|
||||
}
|
||||
|
||||
export const action_errors: ActionError[] = $state([]);
|
||||
|
||||
export function add_error(e: Error, msg: string): void {
|
||||
for (const existing of action_errors) {
|
||||
if (existing.message === msg) {
|
||||
existing.times += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
const action_error = new ActionError(msg, e);
|
||||
action_errors.unshift(action_error);
|
||||
console.log(action_errors.length);
|
||||
}
|
||||
86
daemon/web/src/lib/components/ActionErrors.svelte
Normal file
86
daemon/web/src/lib/components/ActionErrors.svelte
Normal file
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { action_errors } from '../action_errors.svelte';
|
||||
|
||||
let pos = $state(0);
|
||||
let current_error = $derived(action_errors[pos]);
|
||||
|
||||
function prev_error() {
|
||||
if (pos > 0) pos -= 1;
|
||||
else pos = action_errors.length - 1;
|
||||
}
|
||||
function next_error() {
|
||||
if (pos + 1 < action_errors.length) pos += 1;
|
||||
else pos = 0;
|
||||
}
|
||||
function clear_errors() {
|
||||
pos = 0;
|
||||
action_errors.length = 0;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if action_errors.length > 0}
|
||||
<div
|
||||
class="bg-red-100 border-red-100 drop-shadow p-4 flex flex-col gap-2
|
||||
border rounded-md flex-1 justify-between fixed z-10 right-3 bottom-3 ml-3"
|
||||
>
|
||||
<div class="flex flex-row justify-between">
|
||||
<span class="text-xl font-bold mb-2 mr-5 flex flex-row items-center gap-1 text-red-600">
|
||||
<svg
|
||||
class="w-6 h-6 text-red-600"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm11-4a1 1 0 1 0-2 0v5a1 1 0 1 0 2 0V8Zm-1 7a1 1 0 1 0 0 2h.01a1 1 0 1 0 0-2H12Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
Error Completing Action {current_error.times > 1 ? `x${current_error.times}` : ''}
|
||||
</span>
|
||||
<div class="flex items-center mb-2">
|
||||
<span>{pos + 1}/{action_errors.length}</span>
|
||||
<button title="previous error" aria-label="previous error" onclick={prev_error}>
|
||||
<svg aria-hidden="true" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m 15.499979,19.499979 -6.9999997,-7 6.9999997,-6.9999997"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button title="next error" aria-label="next error" onclick={next_error}>
|
||||
<svg aria-hidden="true" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m 8.5000207,5.4999793 7.0000003,6.9999997 -7.0000003,7"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<button title="clear errors" aria-label="clear errors" onclick={clear_errors}>
|
||||
<svg style="width:24px;height:24px" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<span>{current_error.message}</span>
|
||||
{#if current_error.cause}
|
||||
<details>
|
||||
<summary>Details</summary>
|
||||
<code>{current_error.cause}</code>
|
||||
</details>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { req } from '$lib/utils.svelte';
|
||||
import { user_action_req } from '$lib/utils.svelte';
|
||||
|
||||
let {
|
||||
url,
|
||||
@@ -11,6 +11,7 @@
|
||||
icon,
|
||||
onclick,
|
||||
ariaLabel,
|
||||
errorMessage,
|
||||
}: {
|
||||
url: string;
|
||||
method?: string;
|
||||
@@ -21,6 +22,7 @@
|
||||
icon?: any; // Svelte snippet
|
||||
onclick?: () => void | Promise<void>;
|
||||
ariaLabel?: string;
|
||||
errorMessage?: string;
|
||||
} = $props();
|
||||
|
||||
let is_requesting = $state(false);
|
||||
@@ -46,7 +48,11 @@
|
||||
|
||||
is_requesting = true;
|
||||
try {
|
||||
await req(method, url);
|
||||
await user_action_req(
|
||||
method,
|
||||
url,
|
||||
errorMessage ? errorMessage : 'Error performing action'
|
||||
);
|
||||
if (onclick) {
|
||||
await onclick();
|
||||
}
|
||||
|
||||
@@ -7,5 +7,6 @@
|
||||
text="Delete ALL Recordings"
|
||||
prompt={`Are you sure you want to delete ALL recordings?`}
|
||||
url={`/api/delete-all-recordings`}
|
||||
name="all recodings"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { req } from '$lib/utils.svelte';
|
||||
import { user_action_req } from '$lib/utils.svelte';
|
||||
let {
|
||||
text,
|
||||
url,
|
||||
prompt,
|
||||
name,
|
||||
}: {
|
||||
text?: string;
|
||||
url: string;
|
||||
prompt: string;
|
||||
name: string;
|
||||
} = $props();
|
||||
|
||||
function confirmDelete() {
|
||||
if (window.confirm(prompt)) {
|
||||
req('POST', url);
|
||||
user_action_req('POST', url, 'Unable to delete recording ' + name);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
<DeleteButton
|
||||
prompt={`Are you sure you want to delete entry ${entry.name}?`}
|
||||
url={entry.get_delete_url()}
|
||||
name={entry.name}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
<DeleteButton
|
||||
prompt={`Are you sure you want to delete entry ${entry.name}?`}
|
||||
url={entry.get_delete_url()}
|
||||
name={entry.name}
|
||||
/>
|
||||
</td>
|
||||
{/if}
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
variant="blue"
|
||||
onclick={handleReAnalyze}
|
||||
ariaLabel="re-analyze"
|
||||
errorMessage="Error re-analyzing recoding"
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg style="width:20px;height:20px" viewBox="0 0 24 24">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import ApiRequestButton from './ApiRequestButton.svelte';
|
||||
|
||||
let {
|
||||
server_is_recording,
|
||||
}: {
|
||||
@@ -10,7 +9,12 @@
|
||||
|
||||
<div>
|
||||
{#if server_is_recording}
|
||||
<ApiRequestButton url="/api/stop-recording" label="Stop" variant="red">
|
||||
<ApiRequestButton
|
||||
url="/api/stop-recording"
|
||||
label="Stop"
|
||||
variant="red"
|
||||
errorMessage="Error stoppping recording"
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg
|
||||
class="w-6 h-6 text-white"
|
||||
@@ -28,7 +32,12 @@
|
||||
{/snippet}
|
||||
</ApiRequestButton>
|
||||
{:else}
|
||||
<ApiRequestButton url="/api/start-recording" label="Start" variant="blue">
|
||||
<ApiRequestButton
|
||||
url="/api/start-recording"
|
||||
label="Start"
|
||||
variant="blue"
|
||||
errorMessage="Error starting recording"
|
||||
>
|
||||
{#snippet icon()}
|
||||
<svg
|
||||
class="w-6 h-6 text-white"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { add_error } from './action_errors.svelte';
|
||||
import { Manifest } from './manifest.svelte';
|
||||
import type { SystemStats } from './systemStats';
|
||||
|
||||
@@ -31,6 +32,23 @@ export async function req(method: string, url: string): Promise<string> {
|
||||
}
|
||||
}
|
||||
|
||||
// A wrapper around req that reports errors to the UI
|
||||
export async function user_action_req(
|
||||
method: string,
|
||||
url: string,
|
||||
error_msg: string
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
return await req(method, url);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.log('beeeo');
|
||||
add_error(error, error_msg);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function get_manifest(): Promise<Manifest> {
|
||||
const manifest_json = JSON.parse(await req('GET', '/api/qmdl-manifest'));
|
||||
return new Manifest(manifest_json);
|
||||
|
||||
Reference in New Issue
Block a user