Merge pull request #179 from cogwheel0/refactor-auth-provider-and-server-health

refactor-auth-provider-and-server-health
This commit is contained in:
cogwheel
2025-11-26 19:16:50 +05:30
committed by GitHub
11 changed files with 190 additions and 84 deletions

View File

@@ -30,6 +30,7 @@ class ApiAuthInterceptor extends Interceptor {
// Endpoints that have optional authentication (work without but better with)
static const Set<String> _optionalAuthEndpoints = {
'/api/config',
'/api/models',
'/api/v1/configs/models',
};

View File

@@ -81,8 +81,14 @@ class RouterNotifier extends ChangeNotifier {
final activeServer = activeServerAsync.asData?.value;
final hasActiveServer = activeServer != null;
if (!hasActiveServer) {
// Allow auth-related routes while no server configured
if (_isAuthLocation(location)) return null;
// No server configured - redirect to server connection
// Exception: allow staying on server connection or authentication pages
// But always redirect away from connection issue page (user logged out)
if (location == Routes.serverConnection ||
location == Routes.authentication ||
location == Routes.login) {
return null;
}
return Routes.serverConnection;
}

View File

@@ -219,7 +219,7 @@ class ApiService {
return parsed;
}
// Health check
/// Basic health check - just verifies the server is reachable.
Future<bool> checkHealth() async {
try {
final response = await _dio.get('/health');
@@ -229,6 +229,35 @@ class ApiService {
}
}
/// Verifies this is actually an OpenWebUI server by checking the /api/config
/// endpoint for OpenWebUI-specific fields (version, status, features).
///
/// Returns `true` if the server appears to be a valid OpenWebUI instance.
Future<bool> verifyIsOpenWebUIServer() async {
try {
final response = await _dio.get('/api/config');
if (response.statusCode != 200) {
return false;
}
final data = response.data;
if (data is! Map<String, dynamic>) {
return false;
}
// Check for OpenWebUI-specific fields
// The /api/config endpoint always returns these fields on OpenWebUI
final hasStatus = data['status'] == true;
final hasVersion =
data['version'] is String && (data['version'] as String).isNotEmpty;
final hasFeatures = data['features'] is Map;
return hasStatus && hasVersion && hasFeatures;
} catch (e) {
return false;
}
}
// Enhanced health check with model availability
Future<Map<String, dynamic>> checkServerStatus() async {
final result = <String, dynamic>{

View File

@@ -32,10 +32,18 @@ class _ErrorBoundaryState extends ConsumerState<ErrorBoundary> {
void Function(FlutterErrorDetails details)? _previousOnError;
bool _shouldIgnoreError(Object error) {
// Ignore RenderFlex overflow errors (layout issues)
final errorString = error.toString();
return errorString.contains('RenderFlex') ||
errorString.contains('overflow') && errorString.contains('pixels');
// Ignore RenderFlex overflow errors (layout issues)
if (errorString.contains('RenderFlex') ||
errorString.contains('overflow') && errorString.contains('pixels')) {
return true;
}
// Ignore "Build scheduled during frame" errors - these are harmless
// framework warnings from animations during layout
if (errorString.contains('Build scheduled during frame')) {
return true;
}
return false;
}
void _scheduleHandleError(Object error, StackTrace? stack) {
@@ -59,6 +67,10 @@ class _ErrorBoundaryState extends ConsumerState<ErrorBoundary> {
// Set up Flutter error handling for this widget
_previousOnError = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
// Check if this is a harmless error we should completely ignore
if (_shouldIgnoreError(details.exception)) {
return; // Don't forward or handle
}
// Forward to any previously registered handler to avoid interfering
_previousOnError?.call(details);
// Defer handling to avoid setState during build