{#snippet renderWebhookHint(webhook)} {@const displayValue = webhook.url || webhook.path}
{at("settings_provider_webhook_url", {}, "Webhook URL")} {webhook.url ? at( "settings_provider_webhook_url_hint", {}, "Use this URL in the provider webhook settings." ) : at( "settings_provider_webhook_base_missing", { path: webhook.path }, `Set WEBHOOK_BASE_URL to show the full URL for ${webhook.path}.` )}
{displayValue} copyWebhookUrl(webhook)} > {#if copiedWebhookKey === webhook.key} {at("copied", {}, "Copied")} {:else} {at("copy", {}, "Copy")} {/if}
{/snippet} {#snippet renderGroupedFields(section, group)} {@const fieldGroups = semanticFieldGroups(section, group)} {#if fieldGroups.length === 1 && !fieldGroups[0].titleKey} {#each fieldGroups[0].fields as field} {@render renderField(field)} {/each} {:else}
{#each fieldGroups as fieldGroup}
{#if fieldGroup.titleKey}
{fieldGroupTitle(fieldGroup)} {#if fieldGroupDescription(fieldGroup)} {fieldGroupDescription(fieldGroup)} {/if}
{/if}
{#each fieldGroup.fields as field} {@render renderField(field)} {/each}
{/each}
{/if} {/snippet} {#snippet renderField(field)} {@const revealed = isSecretRevealed(field.key)}
{fieldLabelText(field)} {#if field.secret} {at("settings_badge_secret", {}, "Secret")} {/if} {#if isOverridden(field)} {at("settings_badge_override", {}, "Override")} {/if} {field.key} {#if fieldDescriptionText(field)} {fieldDescriptionText(field)} {/if}
{#if field.type === "bool"}
setBoolField(field, checked)} class="admin-switch-root" > {valueFor(field) ? at("enabled", {}, "Включено") : at("disabled", {}, "Выключено")}
{:else if field.type === "color"} settingsStore.markDirty(field.key, e.currentTarget.value)} /> settingsStore.markDirty(field.key, e.currentTarget.value)} /> {:else if field.type === "icon"} {@const selectedIconName = iconValue(field)} {@const SelectedIcon = iconComponent(selectedIconName)} openIconPicker(field)} > {#if SelectedIcon} {/if} {iconLabel(field)} {#if !iconIsDefault(field)} settingsStore.markDirty(field.key, "")} > {at("clear", {}, "Clear")} {/if} {:else if field.choices && field.choices.length > 0} settingsStore.markDirty(field.key, value)} /> {:else if field.type === "int" || field.type === "float"} settingsStore.markDirty(field.key, e.currentTarget.value)} /> {:else if field.type === "text"} {:else if field.type === "json"}
handleJsonFile(field, event)} /> {#if valueFor(field)} settingsStore.markDirty(field.key, "")} > {at("clear", {}, "Clear")} {/if}
{:else if field.secret} settingsStore.markDirty(field.key, e.currentTarget.value)} /> toggleSecretReveal(field.key)} > {#if revealed}{:else}{/if} {:else} settingsStore.markDirty(field.key, e.currentTarget.value)} /> {/if} {#if isOverridden(field) || settingsDirty[field.key]} settingsStore.resetField(field)}> {at("reset", {}, "Сбросить")} {/if}
{/snippet} {#if settingsLoading || !visibleSettingsSections.length} {settingsLoading ? at("loading", {}, "Загрузка…") : at("no_data", {}, "Нет данных")} {:else}

{at( "settings_hint", {}, "Изменения в админке имеют приоритет над .env. Кнопка «Сбросить» возвращает значение из переменных окружения." )}

{settingsAllOpen ? at("collapse_all", {}, "Свернуть всё") : at("expand_all", {}, "Развернуть всё")} {#if Object.keys(settingsDirty).length > 0} settingsStore.saveSettings(onSettingsSaved)} disabled={settingsSaving} > {settingsSaving ? at("saving", {}, "Сохранение...") : at("save", {}, "Сохранить")} {/if}
{#each visibleSettingsSections as section} {@const dirtyInSection = section.fields.filter((f) => Boolean(settingsDirty[f.key])).length} {@const overriddenInSection = section.fields.filter((f) => isOverridden(f)).length} {sectionTitle(section.id)} {at( "settings_params_count", { count: section.fields.length }, `${section.fields.length} параметров` )}{#if overriddenInSection} · {at( "settings_overridden_count", { count: overriddenInSection }, `${overriddenInSection} override` )}{/if}{#if dirtyInSection} · {at( "settings_dirty_count", { count: dirtyInSection }, `${dirtyInSection} изм.` )}{/if} {@const groups = groupSectionFields(section)} {@const rootGroup = groups.find((g) => !g.label)} {@const labelGroups = groups.filter((g) => g.label)}
{#if rootGroup} {#if rootGroup.webhook} {@render renderWebhookHint(rootGroup.webhook)} {/if} {@render renderGroupedFields(section, rootGroup)} {/if} {#if labelGroups.length} (settingsOpenSubsections = { ...settingsOpenSubsections, [section.id]: v })} class="admin-subsection-accordion" > {#each labelGroups as group} {@const subDirty = group.fields.filter((f) => Boolean(settingsDirty[f.key]) ).length} {@const subOverridden = group.fields.filter((f) => isOverridden(f)).length} {subsectionTitle(group)} {at( "settings_fields_count", { count: group.fields.length }, `${group.fields.length} полей` )}{#if subOverridden} · {at( "settings_overridden_count", { count: subOverridden }, `${subOverridden} override` )}{/if}{#if subDirty} · {at( "settings_dirty_count", { count: subDirty }, `${subDirty} изм.` )}{/if}
{#if group.webhook} {@render renderWebhookHint(group.webhook)} {/if} {@render renderGroupedFields(section, group)}
{/each}
{/if}
{/each}
{/if}
{#if iconPickerField} {@const currentIconName = iconValue(iconPickerField)} {@const CurrentIcon = iconComponent(currentIconName)}
{at("settings_icon_current", {}, "Current icon")} {iconLabel(iconPickerField)} {#if !iconIsDefault(iconPickerField)} settingsStore.markDirty(iconPickerField.key, "")} > {at("settings_icon_use_default", {}, "Use default")} {/if}
{/if}
{#each filteredIconOptions as iconName} {@const Icon = iconComponent(iconName)} {/each}