Skip to content

Commit

Permalink
Merge pull request #23 from dbadrian/feature/network_status
Browse files Browse the repository at this point in the history
Feature/network status
  • Loading branch information
dbadrian authored Jan 3, 2025
2 parents 586f009 + b8f224c commit 51eaf4c
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 60 deletions.
5 changes: 5 additions & 0 deletions docs/docs/setup/hetzner.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ http {
}
location / {
# check if backend is alive
auth_request /api/v1/info; # should yield 2xx http status code
error_page 500 =503 @status_offline;
# checks for static file, if not found proxy to app
try_files $uri @proxy_to_app;
}
Expand Down
99 changes: 99 additions & 0 deletions frontend/lib/api/api_status_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import 'dart:async';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:zest/api/api_utils.dart';
import 'package:zest/settings/settings_provider.dart';

part 'api_status_provider.g.dart'; // Generated file

@riverpod
class ApiStatus extends _$ApiStatus {
Timer? _timer;

@override
Future<bool> build() async {
startChecking(const Duration(seconds: 5));
return await _checkBackendStatus();
}

Future<bool> _checkBackendStatus() async {
final SettingsState settings = ref.read(settingsProvider);

try {
final response =
await http.get(getAPIUrl(settings, "/info", withPostSlash: false));
return response.statusCode == 200; // Online if status is 200
} catch (_) {
return false; // Offline in case of errors
}
}

// Start periodic checks
void startChecking(Duration interval) {
_timer?.cancel(); // Cancel existing timer
_timer = Timer.periodic(interval, (_) async {
state = await AsyncValue.guard(() => _checkBackendStatus());
});
// Immediately update state
_updateStatus();
}

// Update the status once without waiting for the periodic interval
Future<void> _updateStatus() async {
state = await AsyncValue.guard(() => _checkBackendStatus());
}

Future<void> updateStatus(bool isOnline) async {
state = AsyncValue.data(isOnline);
}
}

// @riverpod
// class ApiStatus extends AsyncNotifier<bool> {
// Timer? _timer;

// @override
// Future<bool> build() async {
// // Initial state when the provider is first created
// return _checkBackendStatus();
// }

// // Function to check backend status
// Future<bool> _checkBackendStatus() async {
// try {
// final response =
// await http.get(Uri.parse('https://dbadrian/api/v1/info'));
// return response.statusCode == 200; // Online if status is 200
// } catch (_) {
// return false; // Offline in case of errors
// }
// }

// // Start periodic checks
// void startChecking(Duration interval) {
// _timer?.cancel(); // Cancel existing timer
// _timer = Timer.periodic(interval, (_) async {
// state = await AsyncValue.guard(() => _checkBackendStatus());
// });
// // Immediately update state
// _updateStatus();
// }

// // Update the status once without waiting for the periodic interval
// Future<void> _updateStatus() async {
// state = await AsyncValue.guard(() => _checkBackendStatus());
// }

// // Stop periodic checks
// void stopChecking() {
// _timer?.cancel();
// _timer = null;
// }

// // @override
// // void dispose() {
// // stopChecking();
// // super.dispose();
// // }
// }
4 changes: 2 additions & 2 deletions frontend/lib/api/api_utils.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import 'package:zest/settings/settings_provider.dart';

Uri getAPIUrl(SettingsState settings, String path,
{Map<String, dynamic>? queryParameters}) {
{Map<String, dynamic>? queryParameters, withPostSlash = true}) {
final midSlash = path.startsWith('/') ? '' : '/';
final postSlash = path.endsWith('/') ? '' : '/';
final postSlash = path.endsWith('/') || !withPostSlash ? '' : '/';
return Uri.parse('${settings.apiUrl}$midSlash$path$postSlash')
.replace(queryParameters: queryParameters);
}
8 changes: 7 additions & 1 deletion frontend/lib/recipes/controller/edit_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sqflite/sqflite.dart';
import 'package:zest/api/api_service.dart';
import 'package:zest/api/api_status_provider.dart';
import 'package:zest/main.dart';
import 'package:zest/recipes/controller/draft_controller.dart';
import 'package:zest/recipes/screens/recipe_search.dart';
import 'package:zest/settings/settings_provider.dart';
import 'package:zest/utils/utils.dart';

Expand Down Expand Up @@ -217,7 +219,11 @@ class RecipeEditController extends _$RecipeEditController {
// TODO: onconfirm
);
} else if (categories.error is ServerNotReachableException) {
openServerNotAvailableDialog();
openServerNotAvailableDialog(onPressed: () {
ref.read(apiStatusProvider.notifier).updateStatus(false);
shellNavigatorKey.currentState!.overlay!.context
.goNamed(RecipeSearchPage.routeName);
});
}
}
// express the list as a map
Expand Down
30 changes: 26 additions & 4 deletions frontend/lib/ui/main_scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:downloadsfolder/downloadsfolder.dart';
import 'package:path/path.dart' as p;

import 'package:zest/api/api_service.dart';
import 'package:zest/api/api_status_provider.dart';
import 'package:zest/main.dart';
import 'package:zest/recipes/controller/search_controller.dart';
import 'package:zest/recipes/screens/recipe_draft_search.dart';
Expand Down Expand Up @@ -39,21 +40,42 @@ class MainScaffold extends ConsumerWidget {
.select((value) => value.isAuthenticated));
final user = ref.watch(authenticationServiceProvider.notifier
.select((value) => value.whoIsUser));

final backendStatus = ref.watch(apiStatusProvider);

return Scaffold(
key: const Key('mainScaffold'),
body: child,
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.primary,
// title: const Text("Zest"),
title: backendStatus.value ?? false
? Container()
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.cloud_off_rounded,
//set warning colors
color: Theme.of(context).colorScheme.onInverseSurface,
),
const SizedBox(width: 5),
Text("Offline",
style: TextStyle(
color: Theme.of(context).colorScheme.onInverseSurface,
fontWeight: FontWeight.w600)),
],
),
centerTitle: true,
actions: [
if (isAuthenticated)
IconButton(
icon: const Icon(
key: Key("appbar_addrecipe_icon"), Icons.add_card_rounded),
onPressed: () {
context.goNamed(RecipeEditPage.routeNameCreate);
},
onPressed: (backendStatus.value ?? false)
? () {
context.goNamed(RecipeEditPage.routeNameCreate);
}
: null,
),
if (isAuthenticated)
IconButton(
Expand Down
32 changes: 0 additions & 32 deletions frontend/lib/utils/default_scaffold.dart

This file was deleted.

21 changes: 16 additions & 5 deletions frontend/lib/utils/networking.dart
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,22 @@ class ResourceNotFoundInterceptor extends InterceptorContract {
@override
Future<BaseResponse> interceptResponse(
{required BaseResponse response}) async {
if (response.statusCode == 404) {
if (response is Response) {
final ret = jsonDecodeResponseData(response);
throw ResourceNotFoundException(message: ret.toString());
}
// if (response.statusCode == 404) {
// if (response is Response) {
// final ret = jsonDecodeResponseData(response);
// throw ResourceNotFoundException(message: ret.toString());
// }
// }

switch (response.statusCode) {
case 404:
throw ResourceNotFoundException();
case 401:
throw NotAuthorizedException();
case 400:
throw BadRequestException();
case 500:
throw ServerNotReachableException(); // "Internal Server Error [500]"
}
return response;
}
Expand Down
32 changes: 16 additions & 16 deletions frontend/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -372,10 +372,10 @@ packages:
dependency: "direct main"
description:
name: flutter_form_builder
sha256: "39aee5a2548df0b3979a83eea38468116a888341fbca8a92c4be18a486a7bb57"
sha256: "375da52998c72f80dec9187bd93afa7ab202b89d5d066699368ff96d39fd4876"
url: "https://pub.dev"
source: hosted
version: "9.6.0"
version: "9.7.0"
flutter_hooks:
dependency: "direct main"
description:
Expand Down Expand Up @@ -468,26 +468,26 @@ packages:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0"
sha256: "1913841ac4c7bf57cd2e05b717e1fbff7841b542962feff827b16525a781b3e4"
url: "https://pub.dev"
source: hosted
version: "9.2.2"
version: "9.2.3"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b"
sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
flutter_secure_storage_macos:
dependency: transitive
description:
name: flutter_secure_storage_macos
sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81"
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.1.3"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
Expand Down Expand Up @@ -651,10 +651,10 @@ packages:
dependency: transitive
description:
name: http_parser
sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360"
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
version: "4.1.2"
image:
dependency: transitive
description:
Expand Down Expand Up @@ -1080,10 +1080,10 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93"
sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.3.5"
shared_preferences_android:
dependency: transitive
description:
Expand Down Expand Up @@ -1261,10 +1261,10 @@ packages:
dependency: transitive
description:
name: sqflite_darwin
sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474"
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
version: "2.4.1+1"
sqflite_platform_interface:
dependency: transitive
description:
Expand Down Expand Up @@ -1597,10 +1597,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "8b338d4486ab3fbc0ba0db9f9b4f5239b6697fcee427939a40e720cbb9ee0a69"
sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29"
url: "https://pub.dev"
source: hosted
version: "5.9.0"
version: "5.10.0"
window_manager:
dependency: transitive
description:
Expand Down

0 comments on commit 51eaf4c

Please sign in to comment.