271 lines
9.3 KiB
Svelte
271 lines
9.3 KiB
Svelte
<!-- [DEF:SettingsPage:Page] -->
|
|
<script>
|
|
/**
|
|
* @TIER: CRITICAL
|
|
* @PURPOSE: Consolidated Settings Page - All settings in one place with tabbed navigation
|
|
* @LAYER: UI
|
|
* @RELATION: BINDS_TO -> sidebarStore
|
|
* @INVARIANT: Always shows tabbed interface with all settings categories
|
|
*
|
|
* @UX_STATE: Loading -> Shows skeleton loader
|
|
* @UX_STATE: Loaded -> Shows tabbed settings interface
|
|
* @UX_STATE: Error -> Shows error banner with retry button
|
|
* @UX_FEEDBACK: Toast notifications on save success/failure
|
|
* @UX_RECOVERY: Refresh button reloads settings data
|
|
*/
|
|
|
|
import { onMount } from 'svelte';
|
|
import { t } from '$lib/i18n';
|
|
import { api } from '$lib/api.js';
|
|
import { addToast } from '$lib/toasts';
|
|
|
|
// State
|
|
let activeTab = 'environments';
|
|
let settings = null;
|
|
let isLoading = true;
|
|
let error = null;
|
|
|
|
// Load settings on mount
|
|
onMount(async () => {
|
|
await loadSettings();
|
|
});
|
|
|
|
// Load consolidated settings from API
|
|
async function loadSettings() {
|
|
isLoading = true;
|
|
error = null;
|
|
try {
|
|
const response = await api.getConsolidatedSettings();
|
|
settings = response;
|
|
} catch (err) {
|
|
error = err.message || 'Failed to load settings';
|
|
console.error('[SettingsPage][Coherence:Failed]', err);
|
|
} finally {
|
|
isLoading = false;
|
|
}
|
|
}
|
|
|
|
// Handle tab change
|
|
function handleTabChange(tab) {
|
|
activeTab = tab;
|
|
}
|
|
|
|
// Get tab class
|
|
function getTabClass(tab) {
|
|
return activeTab === tab
|
|
? 'text-blue-600 border-b-2 border-blue-600'
|
|
: 'text-gray-600 hover:text-gray-800 border-transparent hover:border-gray-300';
|
|
}
|
|
|
|
// Handle save
|
|
async function handleSave() {
|
|
console.log('[SettingsPage][Action] Saving settings');
|
|
try {
|
|
await api.updateConsolidatedSettings(settings);
|
|
addToast($t.settings?.save_success || 'Settings saved', 'success');
|
|
} catch (err) {
|
|
console.error('[SettingsPage][Coherence:Failed]', err);
|
|
addToast($t.settings?.save_failed || 'Failed to save settings', 'error');
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.container {
|
|
@apply max-w-7xl mx-auto px-4 py-6;
|
|
}
|
|
|
|
.header {
|
|
@apply flex items-center justify-between mb-6;
|
|
}
|
|
|
|
.title {
|
|
@apply text-2xl font-bold text-gray-900;
|
|
}
|
|
|
|
.refresh-btn {
|
|
@apply px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors;
|
|
}
|
|
|
|
.error-banner {
|
|
@apply bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4 flex items-center justify-between;
|
|
}
|
|
|
|
.retry-btn {
|
|
@apply px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors;
|
|
}
|
|
|
|
.tabs {
|
|
@apply border-b border-gray-200 mb-6;
|
|
}
|
|
|
|
.tab-btn {
|
|
@apply px-4 py-2 text-sm font-medium transition-colors focus:outline-none;
|
|
}
|
|
|
|
.tab-content {
|
|
@apply bg-white rounded-lg p-6 border border-gray-200;
|
|
}
|
|
|
|
.skeleton {
|
|
@apply animate-pulse bg-gray-200 rounded;
|
|
}
|
|
</style>
|
|
|
|
<div class="container">
|
|
<!-- Header -->
|
|
<div class="header">
|
|
<h1 class="title">{$t.settings?.title || 'Settings'}</h1>
|
|
<button class="refresh-btn" on:click={loadSettings}>
|
|
{$t.common?.refresh || 'Refresh'}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Error Banner -->
|
|
{#if error}
|
|
<div class="error-banner">
|
|
<span>{error}</span>
|
|
<button class="retry-btn" on:click={loadSettings}>
|
|
{$t.common?.retry || 'Retry'}
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Loading State -->
|
|
{#if isLoading}
|
|
<div class="tab-content">
|
|
<div class="skeleton h-8"></div>
|
|
<div class="skeleton h-8"></div>
|
|
<div class="skeleton h-8"></div>
|
|
<div class="skeleton h-8"></div>
|
|
<div class="skeleton h-8"></div>
|
|
</div>
|
|
{:else if settings}
|
|
<!-- Tabs -->
|
|
<div class="tabs">
|
|
<button
|
|
class="tab-btn {getTabClass('environments')}"
|
|
on:click={() => handleTabChange('environments')}
|
|
>
|
|
{$t.settings?.environments || 'Environments'}
|
|
</button>
|
|
<button
|
|
class="tab-btn {getTabClass('connections')}"
|
|
on:click={() => handleTabChange('connections')}
|
|
>
|
|
{$t.settings?.connections || 'Connections'}
|
|
</button>
|
|
<button
|
|
class="tab-btn {getTabClass('llm')}"
|
|
on:click={() => handleTabChange('llm')}
|
|
>
|
|
{$t.settings?.llm || 'LLM'}
|
|
</button>
|
|
<button
|
|
class="tab-btn {getTabClass('logging')}"
|
|
on:click={() => handleTabChange('logging')}
|
|
>
|
|
{$t.settings?.logging || 'Logging'}
|
|
</button>
|
|
<button
|
|
class="tab-btn {getTabClass('storage')}"
|
|
on:click={() => handleTabChange('storage')}
|
|
>
|
|
{$t.settings?.storage || 'Storage'}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="tab-content">
|
|
{#if activeTab === 'environments'}
|
|
<!-- Environments Tab -->
|
|
<div class="text-lg font-medium mb-4">
|
|
<h2 class="text-xl font-bold mb-4">{$t.settings?.environments || 'Superset Environments'}</h2>
|
|
<p class="text-gray-600 mb-6">
|
|
{$t.settings?.env_description || 'Configure Superset environments for dashboards and datasets.'}
|
|
</p>
|
|
<div class="flex justify-end mb-6">
|
|
<button class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
|
|
{$t.settings?.env_add || 'Add Environment'}
|
|
</button>
|
|
</div>
|
|
{#if settings.environments && settings.environments.length > 0}
|
|
<div class="mt-6">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead class="bg-gray-50">
|
|
<tr>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.connections?.name || "Name"}</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">URL</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.connections?.user || "Username"}</th>
|
|
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Default</th>
|
|
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">{$t.settings?.env_actions || "Actions"}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
{#each settings.environments as env}
|
|
<tr>
|
|
<td class="px-6 py-4 whitespace-nowrap">{env.name}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">{env.url}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">{env.username}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">{env.is_default ? 'Yes' : 'No'}</td>
|
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
<button class="text-green-600 hover:text-green-900 mr-4" on:click={() => handleTestEnv(env.id)}>
|
|
{$t.settings?.env_test || "Test"}
|
|
</button>
|
|
<button class="text-indigo-600 hover:text-indigo-900 mr-4" on:click={() => editEnv(env)}>
|
|
{$t.common.edit}
|
|
</button>
|
|
<button class="text-red-600 hover:text-red-900" on:click={() => handleDeleteEnv(env.id)}>
|
|
{$t.settings?.env_delete || "Delete"}
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{:else if activeTab === 'connections'}
|
|
<!-- Connections Tab -->
|
|
<div class="text-lg font-medium mb-4">
|
|
<h2 class="text-xl font-bold mb-4">{$t.settings?.connections || 'Database Connections'}</h2>
|
|
<p class="text-gray-600 mb-6">
|
|
{$t.settings?.connections_description || 'Configure database connections for data mapping.'}
|
|
</p>
|
|
</div>
|
|
{:else if activeTab === 'llm'}
|
|
<!-- LLM Tab -->
|
|
<div class="text-lg font-medium mb-4">
|
|
<h2 class="text-xl font-bold mb-4">{$t.settings?.llm || 'LLM Providers'}</h2>
|
|
<p class="text-gray-600 mb-6">
|
|
{$t.settings?.llm_description || 'Configure LLM providers for dataset documentation.'}
|
|
</p>
|
|
<div class="flex justify-end mb-6">
|
|
<button class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700">
|
|
{$t.llm?.add_provider || 'Add Provider'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{:else if activeTab === 'logging'}
|
|
<!-- Logging Tab -->
|
|
<div class="text-lg font-medium mb-4">
|
|
<h2 class="text-xl font-bold mb-4">{$t.settings?.logging || 'Logging Configuration'}</h2>
|
|
<p class="text-gray-600 mb-6">
|
|
{$t.settings?.logging_description || 'Configure logging and task log levels.'}
|
|
</p>
|
|
</div>
|
|
{:else if activeTab === 'storage'}
|
|
<!-- Storage Tab -->
|
|
<div class="text-lg font-medium mb-4">
|
|
<h2 class="text-xl font-bold mb-4">{$t.settings?.storage || 'File Storage Configuration'}</h2>
|
|
<p class="text-gray-600 mb-6">
|
|
{$t.settings?.storage_description || 'Configure file storage paths and patterns.'}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- [/DEF:SettingsPage:Page] -->
|