garm/webapp/src/lib/components/WebhookSection.svelte
Gabriel Adrian Samfira 7f647941f6 Slightly better error handling
Extract error details we get from the API when status code > 2xx.
Also, use toast messages to display the error, properly close delete
modals and prevent full page display of error messages.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2025-08-17 07:34:40 +00:00

173 lines
No EOL
4.8 KiB
Svelte

<script lang="ts">
import { garmApi } from '$lib/api/client.js';
import type { HookInfo } from '$lib/api/generated/api.js';
import { toastStore } from '$lib/stores/toast.js';
import Button from './Button.svelte';
import { createEventDispatcher } from 'svelte';
import { extractAPIError } from '$lib/utils/apiError';
export let entityType: 'repository' | 'organization';
export let entityId: string;
export let entityName: string;
let webhookInfo: HookInfo | null = null;
let loading = false;
let checking = true;
const dispatch = createEventDispatcher<{
webhookStatusChanged: { installed: boolean };
}>();
async function checkWebhookStatus() {
if (!entityId) return;
try {
checking = true;
if (entityType === 'repository') {
webhookInfo = await garmApi.getRepositoryWebhookInfo(entityId);
} else {
webhookInfo = await garmApi.getOrganizationWebhookInfo(entityId);
}
} catch (err) {
// If we get a 404, it means no webhook is installed
if (err && typeof err === 'object' && 'response' in err && (err as any).response?.status === 404) {
webhookInfo = null;
} else {
console.warn('Failed to check webhook status:', err);
webhookInfo = null;
}
} finally {
checking = false;
}
}
async function installWebhook() {
if (!entityId) return;
try {
loading = true;
if (entityType === 'repository') {
await garmApi.installRepositoryWebhook(entityId);
} else {
await garmApi.installOrganizationWebhook(entityId);
}
toastStore.success(
'Webhook Installed',
`Webhook for ${entityType} ${entityName} has been installed successfully.`
);
// Refresh webhook status
await checkWebhookStatus();
dispatch('webhookStatusChanged', { installed: true });
} catch (err) {
toastStore.error(
'Webhook Installation Failed',
err instanceof Error ? err.message : 'Failed to install webhook.'
);
} finally {
loading = false;
}
}
async function uninstallWebhook() {
if (!entityId) return;
try {
loading = true;
if (entityType === 'repository') {
await garmApi.uninstallRepositoryWebhook(entityId);
} else {
await garmApi.uninstallOrganizationWebhook(entityId);
}
toastStore.success(
'Webhook Uninstalled',
`Webhook for ${entityType} ${entityName} has been uninstalled successfully.`
);
// Refresh webhook status
await checkWebhookStatus();
dispatch('webhookStatusChanged', { installed: false });
} catch (err) {
toastStore.error(
'Webhook Uninstall Failed',
extractAPIError(err)
);
} finally {
loading = false;
}
}
// Check webhook status when component mounts or entityId changes
$: if (entityId) {
checkWebhookStatus();
}
$: isInstalled = webhookInfo && webhookInfo.active;
</script>
<div class="bg-white dark:bg-gray-800 shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<div class="flex items-center justify-between">
<div>
<h3 class="text-lg font-medium text-gray-900 dark:text-white">
Webhook Status
</h3>
<div class="mt-1 flex items-center">
{#if checking}
<div class="flex items-center">
<div class="animate-spin rounded-full h-4 w-4 border-b-2 border-blue-600 mr-2"></div>
<span class="text-sm text-gray-500 dark:text-gray-400">Checking...</span>
</div>
{:else if isInstalled}
<div class="flex items-center">
<svg class="w-4 h-4 text-green-500 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
<span class="text-sm text-green-700 dark:text-green-300">Webhook installed</span>
</div>
{#if webhookInfo}
<div class="ml-4 text-xs text-gray-500 dark:text-gray-400">
URL: {webhookInfo.url || 'N/A'}
</div>
{/if}
{:else}
<div class="flex items-center">
<svg class="w-4 h-4 text-gray-400 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm0-2a6 6 0 100-12 6 6 0 000 12zm0-10a1 1 0 011 1v3a1 1 0 01-2 0V7a1 1 0 011-1z" clip-rule="evenodd"/>
</svg>
<span class="text-sm text-gray-500 dark:text-gray-400">No webhook installed</span>
</div>
{/if}
</div>
</div>
<div class="flex space-x-2">
{#if !checking}
{#if isInstalled}
<Button
variant="danger"
size="sm"
disabled={loading}
on:click={uninstallWebhook}
>
{loading ? 'Uninstalling...' : 'Uninstall'}
</Button>
{:else}
<Button
variant="primary"
size="sm"
disabled={loading}
on:click={installWebhook}
>
{loading ? 'Installing...' : 'Install Webhook'}
</Button>
{/if}
{/if}
</div>
</div>
</div>
</div>