Files
Ali Khokhar 51157f91bd Refactor admin config into catalog-driven package (#926)
## Problem

Admin config was a single responsibility hub with manually duplicated
provider metadata. Provider labels, fields, template loading,
validation, persistence, and status lived in one place.

## Changes

| Before | After |
| --- | --- |
| Admin config lived in one large `api/admin_config.py` module. | Admin
config lives in package modules for manifest, sources, values,
validation, persistence, and status. |
| Provider admin fields and UI labels were manually duplicated. |
Provider admin fields and display names derive from `PROVIDER_CATALOG`
with admin-only help overrides. |
| `fcc-init` and Admin UI loaded `.env.example` separately. | `fcc-init`
and Admin UI use shared `config.env_template` loading. |
| Architecture docs pointed to the old admin config module. |
Architecture docs describe the package owners and catalog-driven
provider manifest. |

<!-- greptile_comment -->

<details open><summary><h3>Greptile Summary</h3></summary>

This PR refactors admin configuration into a catalog-driven package. The
main changes are:

- Split the former monolithic `api/admin_config.py` into manifest,
source loading, value presentation, validation, persistence, and
provider status modules.
- Generate provider admin fields and display names from
`PROVIDER_CATALOG` with admin-specific help overrides.
- Share `.env.example` loading between `fcc-init` and Admin UI defaults
through `config.env_template`.
- Update admin routes, Admin UI provider labels, architecture docs,
version metadata, and contract/API tests for the new module layout.
</details>

<h3>Confidence Score: 5/5</h3>

The refactor appears merge-safe with no code issues identified in the
reviewed changes.

The package split, catalog-driven provider metadata, shared environment
template loading, route updates, and tests/docs changes are cohesive and
covered by corresponding contract/API/CLI test updates.

<details><summary><h3><a href="https://www.greptile.com/trex"><img
alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="20" align="absmiddle"></a> T-Rex Logs</h3></summary>

**What T-Rex did**
- T-Rex ran manifest validation for catalog provider before and after
routes, capturing base and head responses and catalog-alignment checks,
and confirmed the validation completed successfully.
- T-Rex evaluated the shared-env-template scenarios, observing the
before run with no config.env\_template module and the after run with
the module present, with patched loader values and all consistency
checks passing, and the run exited with code 0.
- T-Rex executed the package-admin-workflow validation, verifying the
base and after import paths, the load/validate/write workflow produced
matching outputs, and the run completed with exit code 0.

<a
href="https://app.greptile.com/trex/runs/12529845/artifacts"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=1"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"><img
alt="View all artifacts"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=1"
height="32"></picture></a>

<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>
</details>

<sub>Reviews (1): Last reviewed commit: ["Refactor admin config into
catalog-drive..."](https://github.com/alishahryar1/free-claude-code/commit/d6239d7953fce75d435b8d6a20536c1aff53aa88)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=40315222)</sub>

<!-- /greptile_comment -->
2026-06-27 15:37:29 -07:00

116 lines
3.3 KiB
Python

"""Admin config value state and API response assembly."""
from __future__ import annotations
import os
from typing import Any
from config.paths import managed_env_path
from .manifest import FIELD_BY_KEY, FIELDS, SECTIONS, ConfigFieldSpec
from .sources import (
configured_env_files,
dotenv_values_from_file,
explicit_env_path,
is_locked_source,
repo_env_path,
template_values,
)
from .status import provider_config_status
MASKED_SECRET = "********"
ValueState = dict[str, dict[str, Any]]
def normalize_for_env(value: Any) -> str:
"""Normalize a submitted admin value for dotenv persistence."""
if value is None:
return ""
if isinstance(value, bool):
return "true" if value else "false"
return str(value)
def display_value(field: ConfigFieldSpec, value: str) -> str:
"""Return the Admin UI display value for a raw config value."""
if field.secret and value:
return MASKED_SECRET
return value
def load_value_state() -> ValueState:
"""Load effective admin field values and their sources."""
values = template_values()
sources = {key: "template" if key in values else "default" for key in FIELD_BY_KEY}
for source, path in configured_env_files():
file_values = dotenv_values_from_file(path)
for key, value in file_values.items():
if key in FIELD_BY_KEY:
values[key] = value
sources[key] = source
for key in FIELD_BY_KEY:
if key in os.environ:
values[key] = os.environ[key]
sources[key] = "process"
return {
key: {
"value": values.get(key, ""),
"source": sources.get(key, "default"),
}
for key in FIELD_BY_KEY
}
def load_config_response() -> dict[str, Any]:
"""Return manifest and current config values for the admin UI."""
state = load_value_state()
fields: list[dict[str, Any]] = []
for field in FIELDS:
entry = state[field.key]
source = entry["source"]
raw_value = entry["value"]
fields.append(
{
"key": field.key,
"label": field.label,
"section": field.section_id,
"type": field.field_type,
"value": display_value(field, raw_value),
"configured": bool(str(raw_value).strip()),
"source": source,
"locked": is_locked_source(source),
"secret": field.secret,
"advanced": field.advanced,
"restart_required": field.restart_required,
"session_sensitive": field.session_sensitive,
"options": list(field.options),
"description": field.description,
}
)
return {
"sections": [
{
"id": section.section_id,
"label": section.label,
"description": section.description,
"advanced": section.advanced,
}
for section in SECTIONS
],
"fields": fields,
"paths": {
"managed": str(managed_env_path()),
"repo": str(repo_env_path()),
"explicit": str(explicit_env_path()) if explicit_env_path() else None,
},
"provider_status": provider_config_status(state),
}