fix: Prevent TUI corruption from logging and improve sync feedback (#1)
Fix TUI corruption from logging and improve sync feedback
This commit is contained in:
parent
0338fe1b38
commit
44d0913a2e
3
.gitignore
vendored
3
.gitignore
vendored
@ -13,3 +13,6 @@ wheels/
|
|||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
.venv/
|
.venv/
|
||||||
|
|
||||||
|
# Data directory (contains user settings, logs, and database)
|
||||||
|
data/
|
||||||
|
|||||||
63
TROUBLESHOOTING.md
Normal file
63
TROUBLESHOOTING.md
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# Troubleshooting Guide
|
||||||
|
|
||||||
|
## Sync Errors
|
||||||
|
|
||||||
|
### API Key Configuration
|
||||||
|
|
||||||
|
The app requires a valid API key to sync data from the SBA Major Domo API.
|
||||||
|
|
||||||
|
**Option 1: Settings Screen (Recommended)**
|
||||||
|
1. Launch the app: `sba-scout`
|
||||||
|
2. Press `x` to open Settings
|
||||||
|
3. Enter your API key
|
||||||
|
4. Click "Save Settings"
|
||||||
|
|
||||||
|
**Option 2: Edit settings.yaml directly**
|
||||||
|
1. Edit `data/settings.yaml`
|
||||||
|
2. Set `api.api_key` to your actual API key
|
||||||
|
3. Save the file
|
||||||
|
4. Restart the app
|
||||||
|
|
||||||
|
### Viewing Logs
|
||||||
|
|
||||||
|
All errors are now logged to `data/logs/sba_scout.log` instead of the terminal.
|
||||||
|
|
||||||
|
To view recent errors:
|
||||||
|
```bash
|
||||||
|
tail -f data/logs/sba_scout.log
|
||||||
|
```
|
||||||
|
|
||||||
|
To search for specific errors:
|
||||||
|
```bash
|
||||||
|
grep "ERROR" data/logs/sba_scout.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Errors
|
||||||
|
|
||||||
|
**"API key not configured"**
|
||||||
|
- Your API key is missing or set to the placeholder value
|
||||||
|
- Fix: Configure your API key using one of the methods above
|
||||||
|
|
||||||
|
**"Sync failed: Invalid API key"** (401/403 error)
|
||||||
|
- Your API key is invalid or expired
|
||||||
|
- Fix: Get a new API key from the SBA Major Domo system
|
||||||
|
|
||||||
|
**"Sync failed: API error 500"**
|
||||||
|
- The API server is experiencing issues
|
||||||
|
- Fix: Wait a few minutes and try again
|
||||||
|
|
||||||
|
**"Request failed: Connection timeout"**
|
||||||
|
- Network connectivity issues
|
||||||
|
- Fix: Check your internet connection, or increase timeout in settings
|
||||||
|
|
||||||
|
## Getting an API Key
|
||||||
|
|
||||||
|
To get an API key for the SBA Major Domo API:
|
||||||
|
1. Visit the SBA Major Domo system
|
||||||
|
2. Go to your account settings
|
||||||
|
3. Generate a new API key
|
||||||
|
4. Copy the key and paste it into the app settings
|
||||||
|
|
||||||
|
## Need More Help?
|
||||||
|
|
||||||
|
Check the full logs at `data/logs/sba_scout.log` for detailed error messages and stack traces.
|
||||||
@ -112,11 +112,16 @@ class LeagueAPIClient:
|
|||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return response.json()
|
return response.json()
|
||||||
except httpx.HTTPStatusError as e:
|
except httpx.HTTPStatusError as e:
|
||||||
logger.error(f"API error: {e.response.status_code} - {e.response.text}")
|
# Check if this is a Cloudflare HTML error page
|
||||||
raise LeagueAPIError(
|
response_text = e.response.text
|
||||||
f"API request failed: {e.response.text}",
|
if "cloudflare" in response_text.lower() and e.response.status_code == 403:
|
||||||
status_code=e.response.status_code,
|
error_msg = "API blocked by Cloudflare (missing or invalid API key)"
|
||||||
)
|
logger.error(f"API error: {e.response.status_code} - Cloudflare block")
|
||||||
|
else:
|
||||||
|
error_msg = f"API request failed (HTTP {e.response.status_code})"
|
||||||
|
logger.error(f"API error: {e.response.status_code} - {response_text[:200]}")
|
||||||
|
|
||||||
|
raise LeagueAPIError(error_msg, status_code=e.response.status_code)
|
||||||
except httpx.RequestError as e:
|
except httpx.RequestError as e:
|
||||||
logger.error(f"Request error: {e}")
|
logger.error(f"Request error: {e}")
|
||||||
raise LeagueAPIError(f"Request failed: {str(e)}")
|
raise LeagueAPIError(f"Request failed: {str(e)}")
|
||||||
|
|||||||
@ -23,10 +23,20 @@ from .screens.matchup import MatchupScreen
|
|||||||
from .screens.roster import RosterScreen
|
from .screens.roster import RosterScreen
|
||||||
from .screens.settings import SettingsScreen
|
from .screens.settings import SettingsScreen
|
||||||
|
|
||||||
# Configure logging
|
# Configure logging - write to file to avoid interfering with TUI
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
log_dir = Path("data/logs")
|
||||||
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
log_file = log_dir / "sba_scout.log"
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.INFO,
|
level=logging.INFO,
|
||||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler(log_file),
|
||||||
|
# Don't add StreamHandler - it corrupts the TUI
|
||||||
|
],
|
||||||
)
|
)
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -100,6 +110,7 @@ class DashboardScreen(Screen):
|
|||||||
# Status bar
|
# Status bar
|
||||||
with Horizontal(id="status-bar"):
|
with Horizontal(id="status-bar"):
|
||||||
yield Label("Last sync: Never", id="sync-status")
|
yield Label("Last sync: Never", id="sync-status")
|
||||||
|
yield LoadingIndicator(id="sync-loader")
|
||||||
yield Button("Sync Now [s]", id="btn-sync", variant="success")
|
yield Button("Sync Now [s]", id="btn-sync", variant="success")
|
||||||
yield Button("Settings [x]", id="btn-settings", variant="default")
|
yield Button("Settings [x]", id="btn-settings", variant="default")
|
||||||
|
|
||||||
@ -186,28 +197,47 @@ class DashboardScreen(Screen):
|
|||||||
|
|
||||||
async def action_sync_data(self) -> None:
|
async def action_sync_data(self) -> None:
|
||||||
"""Sync data from the league API."""
|
"""Sync data from the league API."""
|
||||||
|
from .api.client import LeagueAPIError
|
||||||
from .api.sync import sync_all
|
from .api.sync import sync_all
|
||||||
|
|
||||||
sync_btn = self.query_one("#btn-sync", Button)
|
sync_btn = self.query_one("#btn-sync", Button)
|
||||||
sync_status = self.query_one("#sync-status", Label)
|
sync_status = self.query_one("#sync-status", Label)
|
||||||
|
sync_loader = self.query_one("#sync-loader", LoadingIndicator)
|
||||||
|
|
||||||
sync_btn.disabled = True
|
sync_btn.disabled = True
|
||||||
|
sync_loader.display = True
|
||||||
sync_status.update("Syncing...")
|
sync_status.update("Syncing...")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
settings = get_settings()
|
||||||
|
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
counts = await sync_all(session, season=13)
|
counts = await sync_all(session, season=settings.team.current_season)
|
||||||
|
|
||||||
sync_status.update(f"Synced: {counts['teams']} teams, {counts['players']} players")
|
sync_status.update(f"Synced: {counts['teams']} teams, {counts['players']} players")
|
||||||
|
|
||||||
# Refresh roster summary
|
# Refresh roster summary
|
||||||
await self.load_roster_summary()
|
await self.load_roster_summary()
|
||||||
|
|
||||||
|
except LeagueAPIError as e:
|
||||||
|
logger.error(f"API error during sync: {e.message}")
|
||||||
|
if "cloudflare" in e.message.lower() or e.status_code == 403:
|
||||||
|
sync_status.update("Sync disabled: API key required")
|
||||||
|
elif e.status_code == 401:
|
||||||
|
sync_status.update("Sync failed: Invalid API key")
|
||||||
|
elif e.status_code:
|
||||||
|
sync_status.update(f"Sync failed: HTTP {e.status_code}")
|
||||||
|
else:
|
||||||
|
sync_status.update(f"Sync failed: {str(e.message)[:40]}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Sync failed: {e}")
|
logger.error(f"Unexpected error during sync: {e}", exc_info=True)
|
||||||
sync_status.update(f"Sync failed: {str(e)[:50]}")
|
# Show a user-friendly truncated message
|
||||||
|
error_msg = str(e).split("\n")[0][:50]
|
||||||
|
sync_status.update(f"Sync failed: {error_msg}")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
sync_loader.display = False
|
||||||
sync_btn.disabled = False
|
sync_btn.disabled = False
|
||||||
|
|
||||||
|
|
||||||
@ -313,6 +343,12 @@ class SBAScoutApp(App):
|
|||||||
width: 1fr;
|
width: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#sync-loader {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 1;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#btn-sync {
|
#btn-sync {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user