Skip to content

Commit 65c1ff8

Browse files
Improve webapp error UX and add setup wizard intro modal
Redesign ErrorState to use a subtle centered layout matching EmptyState, replacing the oversized red banner and broken retry button styling. Remove duplicate inline error banners from instances and templates pages. Fix templates DataTable to pass cacheError and wire up retry. Change action errors (delete, create, update) in instances, credentials, and endpoints pages to show toasts instead of replacing the table. Add an intro modal to the setup wizard explaining what will be configured, dismissible via Begin button, Escape key, or backdrop click, with a "Don't show this again" checkbox persisted to localStorage.
1 parent c1fcefc commit 65c1ff8

6 files changed

Lines changed: 101 additions & 73 deletions

File tree

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,28 @@
11
<script lang="ts">
22
import Button from './Button.svelte';
33
4-
export let title: string = 'Error';
4+
export let title: string = 'Error loading data';
55
export let message: string;
66
export let showRetry: boolean = false;
77
export let onRetry: (() => void) | undefined = undefined;
88
</script>
99

10-
<div class="p-6">
11-
<div class="rounded-md bg-red-50 dark:bg-red-900 p-4">
12-
<div class="flex">
13-
<div class="flex-shrink-0">
14-
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
15-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
16-
</svg>
17-
</div>
18-
<div class="ml-3 flex-1">
19-
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">{title}</h3>
20-
<p class="mt-2 text-sm text-red-700 dark:text-red-300">{message}</p>
21-
{#if showRetry && onRetry}
22-
<div class="mt-3">
23-
<Button
24-
variant="secondary"
25-
size="sm"
26-
on:click={onRetry}
27-
icon="<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15' />"
28-
class="text-red-700 dark:text-red-200 bg-red-100 dark:bg-red-800 hover:bg-red-200 dark:hover:bg-red-700 focus:outline-none focus:bg-red-200 dark:focus:bg-red-700"
29-
>
30-
Retry
31-
</Button>
32-
</div>
33-
{/if}
34-
</div>
10+
<div class="p-6 text-center">
11+
<svg class="mx-auto h-12 w-12 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
12+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
13+
</svg>
14+
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">{title}</h3>
15+
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{message}</p>
16+
{#if showRetry && onRetry}
17+
<div class="mt-4">
18+
<Button
19+
variant="secondary"
20+
size="sm"
21+
on:click={onRetry}
22+
icon="<path stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15' />"
23+
>
24+
Retry
25+
</Button>
3526
</div>
36-
</div>
37-
</div>
27+
{/if}
28+
</div>

webapp/src/routes/credentials/+page.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@
312312
);
313313
closeModals();
314314
} catch (err) {
315-
error = extractAPIError(err);
315+
toastStore.error('Create Failed', extractAPIError(err));
316316
}
317317
}
318318
@@ -345,7 +345,7 @@
345345
);
346346
closeModals();
347347
} catch (err) {
348-
error = extractAPIError(err);
348+
toastStore.error('Update Failed', extractAPIError(err));
349349
}
350350
}
351351

webapp/src/routes/endpoints/+page.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@
355355
);
356356
closeModals();
357357
} catch (err) {
358-
error = extractAPIError(err);
358+
toastStore.error('Create Failed', extractAPIError(err));
359359
}
360360
}
361361
@@ -387,7 +387,7 @@
387387
);
388388
closeModals();
389389
} catch (err) {
390-
error = extractAPIError(err);
390+
toastStore.error('Update Failed', extractAPIError(err));
391391
}
392392
}
393393

webapp/src/routes/instances/+page.svelte

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
`Instance ${instanceToDelete.name} has been deleted successfully.`
106106
);
107107
} catch (err) {
108-
error = extractAPIError(err);
108+
toastStore.error('Delete Failed', extractAPIError(err));
109109
} finally {
110110
showDeleteModal = false;
111111
instanceToDelete = null;
@@ -302,18 +302,6 @@
302302
showAction={false}
303303
/>
304304

305-
{#if error}
306-
<div class="bg-red-50 dark:bg-red-900/50 border border-red-200 dark:border-red-800 rounded-md p-4">
307-
<div class="flex">
308-
<div class="ml-3">
309-
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">Error</h3>
310-
<div class="mt-2 text-sm text-red-700 dark:text-red-300">{error}</div>
311-
</div>
312-
</div>
313-
</div>
314-
{/if}
315-
316-
317305
<DataTable
318306
{columns}
319307
data={paginatedInstances}

webapp/src/routes/setup/+page.svelte

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,38 @@
11
<script lang="ts">
2+
import { onMount } from 'svelte';
23
import { resolve } from '$app/paths';
34
import SetupWizardStepper from '$lib/components/setup/SetupWizardStepper.svelte';
45
import EndpointStep from '$lib/components/setup/EndpointStep.svelte';
56
import CredentialsStep from '$lib/components/setup/CredentialsStep.svelte';
67
import EntityStep from '$lib/components/setup/EntityStep.svelte';
78
import RunnerStep from '$lib/components/setup/RunnerStep.svelte';
89
10+
const SKIP_INTRO_KEY = 'garm-setup-skip-intro';
11+
12+
let showIntro = false;
13+
let dontShowAgain = false;
914
let currentStep = 1;
1015
let completed = false;
1116
17+
onMount(() => {
18+
try {
19+
showIntro = localStorage.getItem(SKIP_INTRO_KEY) !== 'true';
20+
} catch {
21+
showIntro = true;
22+
}
23+
});
24+
25+
function dismissIntro() {
26+
if (dontShowAgain) {
27+
try {
28+
localStorage.setItem(SKIP_INTRO_KEY, 'true');
29+
} catch {
30+
// localStorage unavailable, ignore
31+
}
32+
}
33+
showIntro = false;
34+
}
35+
1236
let wizardState = {
1337
endpointName: '',
1438
forgeType: '' as 'github' | 'gitea' | '',
@@ -89,6 +113,8 @@
89113
}
90114
</script>
91115

116+
<svelte:window on:keydown={(e) => { if (showIntro && e.key === 'Escape') dismissIntro(); }} />
117+
92118
<svelte:head>
93119
<title>Setup - GARM</title>
94120
</svelte:head>
@@ -118,12 +144,59 @@
118144
</ol>
119145
</nav>
120146

147+
{#if showIntro}
148+
<!-- svelte-ignore a11y_click_events_have_key_events -->
149+
<div
150+
class="fixed inset-0 bg-black/30 dark:bg-black/50 z-50 flex items-center justify-center p-4"
151+
on:click={dismissIntro}
152+
role="dialog"
153+
aria-modal="true"
154+
tabindex="-1"
155+
>
156+
<!-- svelte-ignore a11y_click_events_have_key_events -->
157+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
158+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-lg w-full p-6 space-y-4" on:click|stopPropagation role="document">
159+
<div class="flex items-center space-x-3">
160+
<div class="flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-full bg-blue-100 dark:bg-blue-900">
161+
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
162+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
163+
</svg>
164+
</div>
165+
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Set Up Runner Infrastructure</h2>
166+
</div>
167+
168+
<div class="text-sm text-gray-600 dark:text-gray-300 space-y-3">
169+
<p>This wizard will help you connect a forge to self-hosted runners. You'll set up:</p>
170+
<ul class="list-disc list-inside space-y-1.5 ml-1">
171+
<li>A <strong>forge endpoint</strong> (GitHub or Gitea)</li>
172+
<li><strong>Credentials</strong> to authenticate with it</li>
173+
<li>A <strong>repository, organization, or enterprise</strong> to manage</li>
174+
<li>A <strong>pool or scale set</strong> to provision runners</li>
175+
</ul>
176+
</div>
177+
178+
<div class="flex items-center justify-between pt-2">
179+
<label class="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400 cursor-pointer">
180+
<input type="checkbox" bind:checked={dontShowAgain} class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" />
181+
<span>Don't show this again</span>
182+
</label>
183+
<button
184+
on:click={dismissIntro}
185+
class="px-5 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md cursor-pointer focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-offset-gray-800"
186+
>
187+
Begin
188+
</button>
189+
</div>
190+
</div>
191+
</div>
192+
{/if}
193+
121194
{#if !completed}
122195
<!-- Header -->
123196
<div>
124197
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Set Up Runner Infrastructure</h1>
125198
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
126-
Follow the steps below to configure your GitHub Actions runner infrastructure.
199+
Configure your runner infrastructure by selecting or creating the resources below.
127200
</p>
128201
</div>
129202

webapp/src/routes/templates/+page.svelte

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import { garmApi } from '$lib/api/client.js';
66
import type { Template } from '$lib/api/generated/api.js';
77
import PageHeader from '$lib/components/PageHeader.svelte';
8-
import ActionButton from '$lib/components/ActionButton.svelte';
98
import Button from '$lib/components/Button.svelte';
109
import DataTable from '$lib/components/DataTable.svelte';
1110
import { toastStore } from '$lib/stores/toast.js';
@@ -316,36 +315,13 @@
316315
</svelte:fragment>
317316
</PageHeader>
318317

319-
{#if (error || cacheError) && !loading}
320-
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-4 mb-6">
321-
<div class="flex">
322-
<div class="flex-shrink-0">
323-
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
324-
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
325-
</svg>
326-
</div>
327-
<div class="ml-3">
328-
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">
329-
Error loading templates
330-
</h3>
331-
<div class="mt-2 text-sm text-red-700 dark:text-red-300">
332-
{error || cacheError}
333-
</div>
334-
<div class="mt-4">
335-
<ActionButton variant="secondary" size="sm" on:click={retryLoadTemplates}>
336-
Try Again
337-
</ActionButton>
338-
</div>
339-
</div>
340-
</div>
341-
</div>
342-
{/if}
343-
344318
<DataTable
345319
{columns}
346320
data={paginatedTemplates}
347321
{loading}
348-
{error}
322+
error={cacheError || error}
323+
showRetry={!!(cacheError || error)}
324+
on:retry={retryLoadTemplates}
349325
{searchTerm}
350326
searchPlaceholder="Search templates by name, description, type..."
351327
{currentPage}

0 commit comments

Comments
 (0)