718 lines
25 KiB
Svelte
718 lines
25 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";
|
|
import ProviderConfig from "../../components/llm/ProviderConfig.svelte";
|
|
|
|
// State
|
|
let activeTab = "environments";
|
|
let settings = null;
|
|
let isLoading = true;
|
|
let error = null;
|
|
|
|
// Environment editing state
|
|
let editingEnvId = null;
|
|
let isAddingEnv = false;
|
|
let newEnv = {
|
|
id: "",
|
|
name: "",
|
|
url: "",
|
|
username: "",
|
|
password: "",
|
|
is_default: false,
|
|
backup_schedule: {
|
|
enabled: false,
|
|
cron_expression: "0 0 * * *",
|
|
},
|
|
};
|
|
|
|
// 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 global settings save (Logging, Storage)
|
|
async function handleSave() {
|
|
console.log("[SettingsPage][Action] Saving settings");
|
|
try {
|
|
// In a real app we might want to only send the changed section,
|
|
// but updateConsolidatedSettings expects full object or we can use specific endpoints.
|
|
// For now we use the consolidated update.
|
|
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");
|
|
}
|
|
}
|
|
|
|
// Handle environment actions
|
|
async function handleTestEnv(id) {
|
|
console.log(`[SettingsPage][Action] Test environment ${id}`);
|
|
addToast("Testing connection...", "info");
|
|
try {
|
|
const result = await api.testEnvironmentConnection(id);
|
|
if (result.status === "success") {
|
|
addToast("Connection successful", "success");
|
|
} else {
|
|
addToast(`Connection failed: ${result.message}`, "error");
|
|
}
|
|
} catch (err) {
|
|
console.error(
|
|
"[SettingsPage][Coherence:Failed] Error testing connection:",
|
|
err,
|
|
);
|
|
addToast("Failed to test connection", "error");
|
|
}
|
|
}
|
|
|
|
function editEnv(env) {
|
|
console.log(`[SettingsPage][Action] Edit environment ${env.id}`);
|
|
newEnv = JSON.parse(JSON.stringify(env)); // Deep copy
|
|
// Ensure backup_schedule exists
|
|
if (!newEnv.backup_schedule) {
|
|
newEnv.backup_schedule = { enabled: false, cron_expression: "0 0 * * *" };
|
|
}
|
|
editingEnvId = env.id;
|
|
isAddingEnv = false;
|
|
}
|
|
|
|
function resetEnvForm() {
|
|
newEnv = {
|
|
id: "",
|
|
name: "",
|
|
url: "",
|
|
username: "",
|
|
password: "",
|
|
is_default: false,
|
|
backup_schedule: {
|
|
enabled: false,
|
|
cron_expression: "0 0 * * *",
|
|
},
|
|
};
|
|
editingEnvId = null;
|
|
}
|
|
|
|
async function handleAddOrUpdateEnv() {
|
|
try {
|
|
console.log(
|
|
`[SettingsPage][Action] ${editingEnvId ? "Updating" : "Adding"} environment.`,
|
|
);
|
|
|
|
// Basic validation
|
|
if (!newEnv.id || !newEnv.name || !newEnv.url) {
|
|
addToast("Please fill in all required fields (ID, Name, URL)", "error");
|
|
return;
|
|
}
|
|
|
|
if (editingEnvId) {
|
|
await api.updateEnvironment(editingEnvId, newEnv);
|
|
addToast("Environment updated", "success");
|
|
} else {
|
|
await api.addEnvironment(newEnv);
|
|
addToast("Environment added", "success");
|
|
}
|
|
|
|
resetEnvForm();
|
|
editingEnvId = null;
|
|
isAddingEnv = false;
|
|
await loadSettings();
|
|
} catch (error) {
|
|
console.error(
|
|
"[SettingsPage][Coherence:Failed] Failed to save environment:",
|
|
error,
|
|
);
|
|
addToast(error.message || "Failed to save environment", "error");
|
|
}
|
|
}
|
|
|
|
async function handleDeleteEnv(id) {
|
|
if (confirm("Are you sure you want to delete this environment?")) {
|
|
console.log(`[SettingsPage][Action] Delete environment ${id}`);
|
|
try {
|
|
await api.deleteEnvironment(id);
|
|
addToast("Environment deleted", "success");
|
|
await loadSettings();
|
|
} catch (error) {
|
|
console.error(
|
|
"[SettingsPage][Coherence:Failed] Failed to delete environment:",
|
|
error,
|
|
);
|
|
addToast("Failed to delete environment", "error");
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="mx-auto w-full max-w-7xl space-y-6">
|
|
<!-- Header -->
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h1 class="text-2xl font-bold text-gray-900">
|
|
{$t.settings?.title || "Settings"}
|
|
</h1>
|
|
<button
|
|
class="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-primary-hover"
|
|
on:click={loadSettings}
|
|
>
|
|
{$t.common?.refresh || "Refresh"}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Error Banner -->
|
|
{#if error}
|
|
<div
|
|
class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4 flex items-center justify-between"
|
|
>
|
|
<span>{error}</span>
|
|
<button
|
|
class="px-4 py-2 bg-destructive text-white rounded hover:bg-destructive-hover transition-colors"
|
|
on:click={loadSettings}
|
|
>
|
|
{$t.common?.retry || "Retry"}
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Loading State -->
|
|
{#if isLoading}
|
|
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
|
<div class="space-y-3">
|
|
<div class="h-8 animate-pulse rounded bg-slate-200"></div>
|
|
<div class="h-8 animate-pulse rounded bg-slate-200"></div>
|
|
<div class="h-8 animate-pulse rounded bg-slate-200"></div>
|
|
<div class="h-8 animate-pulse rounded bg-slate-200"></div>
|
|
<div class="h-8 animate-pulse rounded bg-slate-200"></div>
|
|
</div>
|
|
</div>
|
|
{:else if settings}
|
|
<!-- Tabs -->
|
|
<div class="border-b border-gray-200 mb-6">
|
|
<button
|
|
class="px-4 py-2 text-sm font-medium transition-colors focus:outline-none {getTabClass(
|
|
'environments',
|
|
)}"
|
|
on:click={() => handleTabChange("environments")}
|
|
>
|
|
{$t.settings?.environments || "Environments"}
|
|
</button>
|
|
<button
|
|
class="px-4 py-2 text-sm font-medium transition-colors focus:outline-none {getTabClass(
|
|
'logging',
|
|
)}"
|
|
on:click={() => handleTabChange("logging")}
|
|
>
|
|
{$t.settings?.logging || "Logging"}
|
|
</button>
|
|
<button
|
|
class="px-4 py-2 text-sm font-medium transition-colors focus:outline-none {getTabClass(
|
|
'connections',
|
|
)}"
|
|
on:click={() => handleTabChange("connections")}
|
|
>
|
|
{$t.settings?.connections || "Connections"}
|
|
</button>
|
|
<button
|
|
class="px-4 py-2 text-sm font-medium transition-colors focus:outline-none {getTabClass(
|
|
'llm',
|
|
)}"
|
|
on:click={() => handleTabChange("llm")}
|
|
>
|
|
{$t.settings?.llm || "LLM"}
|
|
</button>
|
|
<button
|
|
class="px-4 py-2 text-sm font-medium transition-colors focus:outline-none {getTabClass(
|
|
'storage',
|
|
)}"
|
|
on:click={() => handleTabChange("storage")}
|
|
>
|
|
{$t.settings?.storage || "Storage"}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Tab Content -->
|
|
<div class="rounded-xl border border-slate-200 bg-white p-6 shadow-sm">
|
|
{#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>
|
|
|
|
{#if !editingEnvId && !isAddingEnv}
|
|
<div class="flex justify-end mb-6">
|
|
<button
|
|
class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
|
|
on:click={() => {
|
|
isAddingEnv = true;
|
|
resetEnvForm();
|
|
}}
|
|
>
|
|
{$t.settings?.env_add || "Add Environment"}
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if editingEnvId || isAddingEnv}
|
|
<!-- Add/Edit Environment Form -->
|
|
<div class="bg-gray-50 p-6 rounded-lg mb-6 border border-gray-200">
|
|
<h3 class="text-lg font-medium mb-4">
|
|
{editingEnvId ? "Edit" : "Add"} Environment
|
|
</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
for="env_id"
|
|
class="block text-sm font-medium text-gray-700">ID</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="env_id"
|
|
bind:value={newEnv.id}
|
|
disabled={!!editingEnvId}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2 disabled:bg-gray-100 disabled:text-gray-500"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="env_name"
|
|
class="block text-sm font-medium text-gray-700">Name</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="env_name"
|
|
bind:value={newEnv.name}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="env_url"
|
|
class="block text-sm font-medium text-gray-700">URL</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="env_url"
|
|
bind:value={newEnv.url}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="env_user"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Username</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="env_user"
|
|
bind:value={newEnv.username}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="env_pass"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Password</label
|
|
>
|
|
<input
|
|
type="password"
|
|
id="env_pass"
|
|
bind:value={newEnv.password}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
</div>
|
|
<div class="flex items-center mt-6">
|
|
<input
|
|
type="checkbox"
|
|
id="env_default"
|
|
bind:checked={newEnv.is_default}
|
|
class="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<label
|
|
for="env_default"
|
|
class="ml-2 block text-sm text-gray-900"
|
|
>Default Environment</label
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<h3 class="text-lg font-medium mb-4 mt-6">Backup Schedule</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div class="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
id="backup_enabled"
|
|
bind:checked={newEnv.backup_schedule.enabled}
|
|
class="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<label
|
|
for="backup_enabled"
|
|
class="ml-2 block text-sm text-gray-900"
|
|
>Enable Automatic Backups</label
|
|
>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="cron_expression"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Cron Expression</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="cron_expression"
|
|
bind:value={newEnv.backup_schedule.cron_expression}
|
|
placeholder="0 0 * * *"
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
Example: 0 0 * * * (daily at midnight)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 flex gap-2 justify-end">
|
|
<button
|
|
on:click={() => {
|
|
isAddingEnv = false;
|
|
editingEnvId = null;
|
|
resetEnvForm();
|
|
}}
|
|
class="bg-gray-200 text-gray-700 px-4 py-2 rounded hover:bg-gray-300"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
on:click={handleAddOrUpdateEnv}
|
|
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
|
>
|
|
{editingEnvId ? "Update" : "Add"} Environment
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if settings.environments && settings.environments.length > 0}
|
|
<div class="mt-6 overflow-x-auto border border-gray-200 rounded-lg">
|
|
<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">
|
|
{#if env.is_default}
|
|
<span
|
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
|
|
>
|
|
Yes
|
|
</span>
|
|
{:else}
|
|
<span class="text-gray-500">No</span>
|
|
{/if}
|
|
</td>
|
|
<td
|
|
class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"
|
|
>
|
|
<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 || "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>
|
|
{:else if !isAddingEnv}
|
|
<div
|
|
class="mb-4 p-4 bg-yellow-100 border-l-4 border-yellow-500 text-yellow-700"
|
|
>
|
|
<p class="font-bold">Warning</p>
|
|
<p>
|
|
No Superset environments configured. You must add at least one
|
|
environment to perform backups or migrations.
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
</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 class="bg-gray-50 p-6 rounded-lg border border-gray-200">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label
|
|
for="log_level"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Log Level</label
|
|
>
|
|
<select
|
|
id="log_level"
|
|
bind:value={settings.logging.level}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
>
|
|
<option value="DEBUG">DEBUG</option>
|
|
<option value="INFO">INFO</option>
|
|
<option value="WARNING">WARNING</option>
|
|
<option value="ERROR">ERROR</option>
|
|
<option value="CRITICAL">CRITICAL</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="task_log_level"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Task Log Level</label
|
|
>
|
|
<select
|
|
id="task_log_level"
|
|
bind:value={settings.logging.task_log_level}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
>
|
|
<option value="DEBUG">DEBUG</option>
|
|
<option value="INFO">INFO</option>
|
|
<option value="WARNING">WARNING</option>
|
|
<option value="ERROR">ERROR</option>
|
|
</select>
|
|
</div>
|
|
<div class="md:col-span-2">
|
|
<label class="flex items-center">
|
|
<input
|
|
type="checkbox"
|
|
id="enable_belief_state"
|
|
bind:checked={settings.logging.enable_belief_state}
|
|
class="h-4 w-4 text-blue-600 border-gray-300 rounded"
|
|
/>
|
|
<span class="ml-2 block text-sm text-gray-900"
|
|
>Enable Belief State Logging (Beta)</span
|
|
>
|
|
</label>
|
|
<p class="text-xs text-gray-500 mt-1 ml-6">
|
|
Logs agent reasoning and internal state changes for debugging.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 flex justify-end">
|
|
<button
|
|
on:click={handleSave}
|
|
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
|
>
|
|
Save Logging Config
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</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>
|
|
|
|
{#if settings.connections && settings.connections.length > 0}
|
|
<!-- Connections list would go here -->
|
|
<p class="text-gray-500 italic">
|
|
No additional connections configured. Superset database
|
|
connections are used by default.
|
|
</p>
|
|
{:else}
|
|
<div
|
|
class="text-center py-8 bg-gray-50 rounded-lg border border-dashed border-gray-300"
|
|
>
|
|
<p class="text-gray-500">No external connections configured.</p>
|
|
<button
|
|
class="mt-4 px-4 py-2 border border-blue-600 text-blue-600 rounded hover:bg-blue-50"
|
|
>
|
|
Add Connection
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
</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>
|
|
|
|
<ProviderConfig
|
|
providers={settings.llm_providers || []}
|
|
onSave={loadSettings}
|
|
/>
|
|
</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 class="bg-gray-50 p-6 rounded-lg border border-gray-200">
|
|
<div class="grid grid-cols-1 gap-4">
|
|
<div>
|
|
<label
|
|
for="storage_path"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Root Path</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="storage_path"
|
|
bind:value={settings.storage.root_path}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="backup_path"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Backup Path</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="backup_path"
|
|
bind:value={settings.storage.backup_path}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label
|
|
for="repo_path"
|
|
class="block text-sm font-medium text-gray-700"
|
|
>Repository Path</label
|
|
>
|
|
<input
|
|
type="text"
|
|
id="repo_path"
|
|
bind:value={settings.storage.repo_path}
|
|
class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 flex justify-end">
|
|
<button
|
|
on:click={() => handleSave()}
|
|
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
|
>
|
|
Save Storage Config
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- [/DEF:SettingsPage:Page] -->
|