feat(service-integrations): phase 2 — integration widget & app form UI
- Add 6 renderer components: StatCard, Gauge, List, Progress, AlertBanner, Chart - Add IntegrationWidget container with auto-refresh, loading, error states - Add IntegrationAlertOverlay for layout-level critical alerts - Add IntegrationConfigFields for dynamic form generation from Zod schemas - Register integration type in WidgetRenderer - Extend WidgetCreationForm with integration app/endpoint pickers - Extend AppForm with integration config section and test connection button - Add /api/integrations/alerts endpoint
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<script lang="ts">
|
||||
import type { IntegrationFieldDescriptor } from '$lib/server/integrations/types.js';
|
||||
|
||||
interface Props {
|
||||
fields: IntegrationFieldDescriptor[];
|
||||
values: Record<string, unknown>;
|
||||
onchange: (name: string, value: unknown) => void;
|
||||
idPrefix?: string;
|
||||
}
|
||||
|
||||
let { fields, values, onchange, idPrefix = 'int' }: Props = $props();
|
||||
|
||||
const inputClass = 'w-full rounded-lg border border-input bg-background px-3 py-2 text-sm text-foreground placeholder:text-muted-foreground transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-ring/30';
|
||||
</script>
|
||||
|
||||
<div class="space-y-3">
|
||||
{#each fields as field (field.name)}
|
||||
<div>
|
||||
<label for="{idPrefix}-{field.name}" class="mb-1 block text-sm font-medium text-card-foreground">
|
||||
{field.label}
|
||||
{#if field.required}
|
||||
<span class="text-destructive">*</span>
|
||||
{/if}
|
||||
</label>
|
||||
{#if field.type === 'boolean'}
|
||||
<label class="flex items-center gap-2">
|
||||
<input
|
||||
id="{idPrefix}-{field.name}"
|
||||
type="checkbox"
|
||||
checked={!!values[field.name]}
|
||||
onchange={(e) => onchange(field.name, e.currentTarget.checked)}
|
||||
class="h-4 w-4 rounded border-input accent-primary"
|
||||
/>
|
||||
<span class="text-sm text-muted-foreground">{field.description ?? ''}</span>
|
||||
</label>
|
||||
{:else if field.type === 'number'}
|
||||
<input
|
||||
id="{idPrefix}-{field.name}"
|
||||
type="number"
|
||||
value={values[field.name] ?? ''}
|
||||
oninput={(e) => onchange(field.name, parseInt(e.currentTarget.value) || 0)}
|
||||
class={inputClass}
|
||||
placeholder={field.description ?? ''}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
id="{idPrefix}-{field.name}"
|
||||
type={field.name.toLowerCase().includes('password') || field.name.toLowerCase().includes('secret') ? 'password' : 'text'}
|
||||
value={values[field.name] ?? ''}
|
||||
oninput={(e) => onchange(field.name, e.currentTarget.value)}
|
||||
class={inputClass}
|
||||
placeholder={field.description ?? field.label}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
Reference in New Issue
Block a user