Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 19 additions & 28 deletions webapp/src/lib/components/ErrorState.svelte
Original file line number Diff line number Diff line change
@@ -1,37 +1,28 @@
<script lang="ts">
import Button from './Button.svelte';

export let title: string = 'Error';
export let title: string = 'Error loading data';
export let message: string;
export let showRetry: boolean = false;
export let onRetry: (() => void) | undefined = undefined;
</script>

<div class="p-6">
<div class="rounded-md bg-red-50 dark:bg-red-900 p-4">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<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" />
</svg>
</div>
<div class="ml-3 flex-1">
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">{title}</h3>
<p class="mt-2 text-sm text-red-700 dark:text-red-300">{message}</p>
{#if showRetry && onRetry}
<div class="mt-3">
<Button
variant="secondary"
size="sm"
on:click={onRetry}
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' />"
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"
>
Retry
</Button>
</div>
{/if}
</div>
<div class="p-6 text-center">
<svg class="mx-auto h-12 w-12 text-red-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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" />
</svg>
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">{title}</h3>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">{message}</p>
{#if showRetry && onRetry}
<div class="mt-4">
<Button
variant="secondary"
size="sm"
on:click={onRetry}
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' />"
>
Retry
</Button>
</div>
</div>
</div>
{/if}
</div>
4 changes: 2 additions & 2 deletions webapp/src/routes/credentials/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@
);
closeModals();
} catch (err) {
error = extractAPIError(err);
toastStore.error('Create Failed', extractAPIError(err));
}
}

Expand Down Expand Up @@ -345,7 +345,7 @@
);
closeModals();
} catch (err) {
error = extractAPIError(err);
toastStore.error('Update Failed', extractAPIError(err));
}
}

Expand Down
4 changes: 2 additions & 2 deletions webapp/src/routes/endpoints/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@
);
closeModals();
} catch (err) {
error = extractAPIError(err);
toastStore.error('Create Failed', extractAPIError(err));
}
}

Expand Down Expand Up @@ -387,7 +387,7 @@
);
closeModals();
} catch (err) {
error = extractAPIError(err);
toastStore.error('Update Failed', extractAPIError(err));
}
}

Expand Down
14 changes: 1 addition & 13 deletions webapp/src/routes/instances/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
`Instance ${instanceToDelete.name} has been deleted successfully.`
);
} catch (err) {
error = extractAPIError(err);
toastStore.error('Delete Failed', extractAPIError(err));
} finally {
showDeleteModal = false;
instanceToDelete = null;
Expand Down Expand Up @@ -302,18 +302,6 @@
showAction={false}
/>

{#if error}
<div class="bg-red-50 dark:bg-red-900/50 border border-red-200 dark:border-red-800 rounded-md p-4">
<div class="flex">
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">Error</h3>
<div class="mt-2 text-sm text-red-700 dark:text-red-300">{error}</div>
</div>
</div>
</div>
{/if}


<DataTable
{columns}
data={paginatedInstances}
Expand Down
75 changes: 74 additions & 1 deletion webapp/src/routes/setup/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,14 +1,38 @@
<script lang="ts">
import { onMount } from 'svelte';
import { resolve } from '$app/paths';
import SetupWizardStepper from '$lib/components/setup/SetupWizardStepper.svelte';
import EndpointStep from '$lib/components/setup/EndpointStep.svelte';
import CredentialsStep from '$lib/components/setup/CredentialsStep.svelte';
import EntityStep from '$lib/components/setup/EntityStep.svelte';
import RunnerStep from '$lib/components/setup/RunnerStep.svelte';

const SKIP_INTRO_KEY = 'garm-setup-skip-intro';

let showIntro = false;
let dontShowAgain = false;
let currentStep = 1;
let completed = false;

onMount(() => {
try {
showIntro = localStorage.getItem(SKIP_INTRO_KEY) !== 'true';
} catch {
showIntro = true;
}
});

function dismissIntro() {
if (dontShowAgain) {
try {
localStorage.setItem(SKIP_INTRO_KEY, 'true');
} catch {
// localStorage unavailable, ignore
}
}
showIntro = false;
}

let wizardState = {
endpointName: '',
forgeType: '' as 'github' | 'gitea' | '',
Expand Down Expand Up @@ -89,6 +113,8 @@
}
</script>

<svelte:window on:keydown={(e) => { if (showIntro && e.key === 'Escape') dismissIntro(); }} />

<svelte:head>
<title>Setup - GARM</title>
</svelte:head>
Expand Down Expand Up @@ -118,12 +144,59 @@
</ol>
</nav>

{#if showIntro}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class="fixed inset-0 bg-black/30 dark:bg-black/50 z-50 flex items-center justify-center p-4"
on:click={dismissIntro}
role="dialog"
aria-modal="true"
tabindex="-1"
>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<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">
<div class="flex items-center space-x-3">
<div class="flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-full bg-blue-100 dark:bg-blue-900">
<svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<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" />
</svg>
</div>
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Set Up Runner Infrastructure</h2>
</div>

<div class="text-sm text-gray-600 dark:text-gray-300 space-y-3">
<p>This wizard will help you connect a forge to self-hosted runners. You'll set up:</p>
<ul class="list-disc list-inside space-y-1.5 ml-1">
<li>A <strong>forge endpoint</strong> (GitHub or Gitea)</li>
<li><strong>Credentials</strong> to authenticate with it</li>
<li>A <strong>repository, organization, or enterprise</strong> to manage</li>
<li>A <strong>pool or scale set</strong> to provision runners</li>
</ul>
</div>

<div class="flex items-center justify-between pt-2">
<label class="flex items-center space-x-2 text-sm text-gray-500 dark:text-gray-400 cursor-pointer">
<input type="checkbox" bind:checked={dontShowAgain} class="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500" />
<span>Don't show this again</span>
</label>
<button
on:click={dismissIntro}
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"
>
Begin
</button>
</div>
</div>
</div>
{/if}

{#if !completed}
<!-- Header -->
<div>
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Set Up Runner Infrastructure</h1>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
Follow the steps below to configure your GitHub Actions runner infrastructure.
Configure your runner infrastructure by selecting or creating the resources below.
</p>
</div>

Expand Down
30 changes: 3 additions & 27 deletions webapp/src/routes/templates/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import { garmApi } from '$lib/api/client.js';
import type { Template } from '$lib/api/generated/api.js';
import PageHeader from '$lib/components/PageHeader.svelte';
import ActionButton from '$lib/components/ActionButton.svelte';
import Button from '$lib/components/Button.svelte';
import DataTable from '$lib/components/DataTable.svelte';
import { toastStore } from '$lib/stores/toast.js';
Expand Down Expand Up @@ -316,36 +315,13 @@
</svelte:fragment>
</PageHeader>

{#if (error || cacheError) && !loading}
<div class="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-4 mb-6">
<div class="flex">
<div class="flex-shrink-0">
<svg class="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor">
<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" />
</svg>
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800 dark:text-red-200">
Error loading templates
</h3>
<div class="mt-2 text-sm text-red-700 dark:text-red-300">
{error || cacheError}
</div>
<div class="mt-4">
<ActionButton variant="secondary" size="sm" on:click={retryLoadTemplates}>
Try Again
</ActionButton>
</div>
</div>
</div>
</div>
{/if}

<DataTable
{columns}
data={paginatedTemplates}
{loading}
{error}
error={cacheError || error}
showRetry={!!(cacheError || error)}
on:retry={retryLoadTemplates}
{searchTerm}
searchPlaceholder="Search templates by name, description, type..."
{currentPage}
Expand Down