mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-06-01 22:09:04 +02:00
Admin UX refactor, runtime fixes, and startup logging (#472)
## Summary - Refactors the admin interface into focused views and simplifies the header (removes noisy status labels; hides managed-source labels where appropriate). - Fixes Claude runtime settings handling, reduces Z.ai base URL leakage in the admin UI, and streamlines API startup logging. - Updates configuration and catalog behavior (including `.env.example` / README), and expands automated tests around admin, app lifespan, and config/registry behavior. ## Test plan - [ ] `uv run ruff format`, `uv run ruff check`, `uv run ty check`, `uv run pytest` - [ ] Smoke the admin UI: navigation between views, settings save/load, no sensitive URL leakage in the UI - [ ] Confirm API startup logs are readable and not overly verbose in normal operation
This commit is contained in:
@@ -94,11 +94,10 @@ Use the same command to update to the latest version.
|
||||
fcc-server
|
||||
```
|
||||
|
||||
After startup, the terminal prints the proxy and admin URLs:
|
||||
After startup, Uvicorn prints the proxy bind address and the app logs the admin URL:
|
||||
|
||||
```text
|
||||
Server URL: http://127.0.0.1:8082
|
||||
Admin UI: http://127.0.0.1:8082/admin
|
||||
INFO: Admin UI: http://127.0.0.1:8082/admin (local-only)
|
||||
```
|
||||
|
||||
Many terminals make these clickable. Use your configured `PORT` if it is not `8082`.
|
||||
|
||||
@@ -78,7 +78,6 @@ textarea {
|
||||
.brand h1,
|
||||
.brand p,
|
||||
.topbar h2,
|
||||
.topbar p,
|
||||
.section-heading h3,
|
||||
.section-heading p,
|
||||
.strip-header h3 {
|
||||
@@ -91,7 +90,6 @@ textarea {
|
||||
}
|
||||
|
||||
.brand p,
|
||||
.eyebrow,
|
||||
.section-heading p,
|
||||
.action-meta span {
|
||||
color: var(--muted);
|
||||
@@ -136,33 +134,15 @@ textarea {
|
||||
}
|
||||
|
||||
.topbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin-bottom: 4px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.topbar h2 {
|
||||
font-size: 28px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.server-state {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-pill,
|
||||
.model-badge {
|
||||
.status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 30px;
|
||||
@@ -463,15 +443,6 @@ textarea {
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.server-state {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
left: 0;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const state = {
|
||||
config: null,
|
||||
status: null,
|
||||
fields: new Map(),
|
||||
localStatus: new Map(),
|
||||
modelOptions: [],
|
||||
@@ -98,14 +97,9 @@ async function api(path, options = {}) {
|
||||
|
||||
async function load() {
|
||||
showMessage("Loading admin config");
|
||||
const [config, status] = await Promise.all([
|
||||
api("/admin/api/config"),
|
||||
api("/admin/api/status"),
|
||||
]);
|
||||
const config = await api("/admin/api/config");
|
||||
state.config = config;
|
||||
state.status = status;
|
||||
state.fields = new Map(config.fields.map((field) => [field.key, field]));
|
||||
updateHeader(status);
|
||||
renderNav();
|
||||
renderProviders(config.provider_status);
|
||||
renderSections(config.sections, config.fields);
|
||||
@@ -116,13 +110,6 @@ async function load() {
|
||||
showMessage("");
|
||||
}
|
||||
|
||||
function updateHeader(status) {
|
||||
const serverStatus = byId("serverStatus");
|
||||
serverStatus.textContent = "Running";
|
||||
serverStatus.className = "status-pill ok";
|
||||
byId("modelBadge").textContent = status.model || "";
|
||||
}
|
||||
|
||||
function renderNav() {
|
||||
const nav = byId("sectionNav");
|
||||
nav.innerHTML = "";
|
||||
@@ -505,7 +492,5 @@ byId("applyButton").addEventListener("click", apply);
|
||||
byId("refreshLocal").addEventListener("click", refreshLocalStatus);
|
||||
|
||||
load().catch((error) => {
|
||||
byId("serverStatus").textContent = "Error";
|
||||
byId("serverStatus").className = "status-pill error";
|
||||
showMessage(error.message, "error");
|
||||
});
|
||||
|
||||
@@ -23,13 +23,8 @@
|
||||
<main class="main">
|
||||
<header class="topbar">
|
||||
<div>
|
||||
<p class="eyebrow">Local Admin</p>
|
||||
<h2 id="pageTitle">Providers</h2>
|
||||
</div>
|
||||
<div class="server-state">
|
||||
<span id="serverStatus" class="status-pill neutral">Loading</span>
|
||||
<span id="modelBadge" class="model-badge"></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div id="adminViews" class="admin-views">
|
||||
|
||||
+4
-9
@@ -3,15 +3,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from fastapi import FastAPI
|
||||
from loguru import logger
|
||||
|
||||
from api.admin_urls import local_admin_url, local_proxy_root_url
|
||||
from api.admin_urls import local_admin_url
|
||||
from config.settings import Settings, get_settings
|
||||
from providers.exceptions import ServiceUnavailableError
|
||||
from providers.registry import ProviderRegistry
|
||||
@@ -102,7 +102,6 @@ class AppRuntime:
|
||||
|
||||
async def startup(self) -> None:
|
||||
logger.info("Starting Claude Code Proxy...")
|
||||
root_url = local_proxy_root_url(self.settings)
|
||||
admin_url = local_admin_url(self.settings)
|
||||
self._provider_registry = ProviderRegistry()
|
||||
self.app.state.provider_registry = self._provider_registry
|
||||
@@ -112,12 +111,8 @@ class AppRuntime:
|
||||
self._provider_registry.start_model_list_refresh(self.settings)
|
||||
await self._start_messaging_if_configured()
|
||||
self._publish_state()
|
||||
logger.info("Server URL: {}", root_url)
|
||||
logger.info("Admin UI: {} (local-only)", admin_url)
|
||||
print(
|
||||
f"Server URL: {root_url}\nAdmin UI: {admin_url} (local-only)",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
logging.getLogger("uvicorn.error").info(
|
||||
"Admin UI: %s (local-only)", admin_url
|
||||
)
|
||||
except Exception as exc:
|
||||
log_startup_failure(self.settings, exc)
|
||||
|
||||
@@ -59,6 +59,28 @@ def test_admin_page_no_longer_renders_generated_env_panel(monkeypatch, tmp_path)
|
||||
assert "envPreview" not in response.text
|
||||
|
||||
|
||||
def test_admin_page_no_longer_renders_global_status_header(monkeypatch, tmp_path):
|
||||
_set_home(monkeypatch, tmp_path)
|
||||
app = create_app(lifespan_enabled=False)
|
||||
|
||||
response = _local_client(app).get("/admin")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Local Admin" not in response.text
|
||||
assert "serverStatus" not in response.text
|
||||
assert "modelBadge" not in response.text
|
||||
|
||||
|
||||
def test_admin_static_no_longer_fetches_global_status_header():
|
||||
script = Path("api/admin_static/admin.js").read_text(encoding="utf-8")
|
||||
|
||||
assert 'api("/admin/api/status")' not in script
|
||||
assert "updateHeader" not in script
|
||||
assert '"Running"' not in script
|
||||
assert "serverStatus" not in script
|
||||
assert "modelBadge" not in script
|
||||
|
||||
|
||||
def test_admin_static_hides_managed_source_label():
|
||||
script = Path("api/admin_static/admin.js").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@@ -64,6 +64,53 @@ def test_warn_if_process_auth_token_skips_explicit_dotenv_config():
|
||||
warning.assert_not_called()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_runtime_startup_logs_admin_url_without_printed_server_banner(tmp_path):
|
||||
import api.runtime as api_runtime_mod
|
||||
|
||||
settings = _app_settings(
|
||||
messaging_platform="none",
|
||||
telegram_bot_token=None,
|
||||
allowed_telegram_user_id=None,
|
||||
discord_bot_token=None,
|
||||
allowed_discord_channels=None,
|
||||
allowed_dir=str(tmp_path / "workspace"),
|
||||
claude_workspace=str(tmp_path / "data"),
|
||||
host="127.0.0.1",
|
||||
port=9099,
|
||||
)
|
||||
runtime = api_runtime_mod.AppRuntime(
|
||||
app=FastAPI(), settings=cast(Settings, settings)
|
||||
)
|
||||
uvicorn_logger = MagicMock()
|
||||
|
||||
with (
|
||||
patch("builtins.print") as printed,
|
||||
patch.object(
|
||||
api_runtime_mod.logging, "getLogger", return_value=uvicorn_logger
|
||||
) as get_logger,
|
||||
patch.object(api_runtime_mod.logger, "info") as app_info,
|
||||
patch.object(ProviderRegistry, "validate_configured_models", new=AsyncMock()),
|
||||
patch.object(ProviderRegistry, "start_model_list_refresh"),
|
||||
patch.object(ProviderRegistry, "cleanup", new=AsyncMock()),
|
||||
patch(
|
||||
"messaging.platforms.factory.create_messaging_platform",
|
||||
return_value=None,
|
||||
),
|
||||
):
|
||||
await runtime.startup()
|
||||
await runtime.shutdown()
|
||||
|
||||
printed.assert_not_called()
|
||||
get_logger.assert_called_with("uvicorn.error")
|
||||
uvicorn_logger.info.assert_called_once_with(
|
||||
"Admin UI: %s (local-only)",
|
||||
"http://127.0.0.1:9099/admin",
|
||||
)
|
||||
logged = " ".join(str(arg) for call in app_info.call_args_list for arg in call.args)
|
||||
assert "Server URL:" not in logged
|
||||
|
||||
|
||||
def test_create_app_provider_error_handler_returns_anthropic_format():
|
||||
from api.app import create_app
|
||||
from providers.exceptions import AuthenticationError
|
||||
|
||||
Reference in New Issue
Block a user