diff --git a/lib/logger.dart b/lib/logger.dart new file mode 100644 index 0000000..920d20b --- /dev/null +++ b/lib/logger.dart @@ -0,0 +1,27 @@ +import 'dart:io'; + +import 'package:logger/logger.dart'; + +class Log { + static final Log _instance = Log._internal(); + + static Logger _logger = Logger(); + + factory Log() { + _logger = Logger( + output: MultiOutput([ + ConsoleOutput(), + FileOutput(file: File("/var/log/proxmox-dash/proxmox-dash.log")), + ]), + level: Level.info, + ); + return _instance; + } + + void debug(m) => _logger.d(m); + void info(m) => _logger.i(m); + void warn(m) => _logger.w(m); + void error(m) => _logger.e(m); + + Log._internal(); +} diff --git a/lib/main.dart b/lib/main.dart index eb568f2..42423c8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,20 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:pi_dashboard/logger.dart'; import 'src/app.dart'; import 'src/settings/settings_controller.dart'; import 'src/settings/settings_service.dart'; void main() async { - // Set up the SettingsController, which will glue user settings to multiple - // Flutter Widgets. final settingsController = SettingsController(SettingsService()); - - // Load the user's preferred theme while the splash screen is displayed. - // This prevents a sudden theme change when the app is first displayed. await settingsController.loadSettings(); - // Run the app and pass in the SettingsController. The app listens to the - // SettingsController for changes, then passes it further down to the - // SettingsView. + Log().info("Application started"); runApp(MyApp(settingsController: settingsController)); + Log().info("Application stopped"); } diff --git a/lib/src/app.dart b/lib/src/app.dart index 45e18c0..2586750 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -15,33 +15,19 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - // Glue the SettingsController to the MaterialApp. - // - // The ListenableBuilder Widget listens to the SettingsController for changes. - // Whenever the user updates their settings, the MaterialApp is rebuilt. return ListenableBuilder( listenable: settingsController, builder: (BuildContext context, Widget? child) { return MaterialApp( - // Providing a restorationScopeId allows the Navigator built by the - // MaterialApp to restore the navigation stack when a user leaves and - // returns to the app after it has been killed while running in the - // background. restorationScopeId: 'app', supportedLocales: const [ Locale('en', ''), // English, no country code ], - - // Define a light and dark color theme. Then, read the user's - // preferred ThemeMode (light, dark, or system default) from the - // SettingsController to display the correct theme. theme: ThemeData(), darkTheme: ThemeData.dark(), themeMode: settingsController.themeMode, - // Define a function to handle named routes in order to support - // Flutter web url navigation and deep linking. onGenerateRoute: (RouteSettings routeSettings) { return MaterialPageRoute( settings: routeSettings, diff --git a/lib/src/proxmox_lister/proxmox_lister_list_view.dart b/lib/src/proxmox_lister/proxmox_lister_list_view.dart index 21cb982..b743c9a 100644 --- a/lib/src/proxmox_lister/proxmox_lister_list_view.dart +++ b/lib/src/proxmox_lister/proxmox_lister_list_view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:pi_dashboard/logger.dart'; import 'package:pi_dashboard/src/proxmox_webservice/model.dart'; import 'package:pi_dashboard/src/proxmox_webservice/service.dart'; import 'package:pi_dashboard/src/screen_helper.dart'; @@ -51,11 +52,13 @@ class _ProxmoxListerState extends State { if (!success) { return Future.error(Exception("couldn't authenticate against Proxmox")); } + Log().info("Server auth successful"); ProxmoxNodeMap map = {}; final nodes = await _service.listNodes(); for (final node in nodes) { map[node] = await _service.listVms(node.node); + Log().debug("received node [${node.node}] with ${map[node]?.length} vms"); } return map; } @@ -77,6 +80,7 @@ class _ProxmoxListerState extends State { timeout: const Duration(seconds: 60), onExpire: () async { await turnOffScreen(); + Log().info("No input for 60 seconds. Screen turned off"); }, child: Stack( children: [ diff --git a/lib/src/proxmox_webservice/service.dart b/lib/src/proxmox_webservice/service.dart index b164cb5..0433b77 100644 --- a/lib/src/proxmox_webservice/service.dart +++ b/lib/src/proxmox_webservice/service.dart @@ -1,6 +1,7 @@ import 'dart:convert' as convert; import 'package:http/http.dart' as http; +import 'package:pi_dashboard/logger.dart'; import 'model.dart'; class ProxmoxWebService { @@ -40,6 +41,8 @@ class ProxmoxWebService { _authenticated = true; } else { _authenticated = false; + Log().info( + "Authentication returned error code ${resp.statusCode}: ${resp.body}"); } return _authenticated; @@ -57,7 +60,10 @@ class ProxmoxWebService { "Cookie": "PVEAuthCookie=$_ticket" }); - if (resp.statusCode != 200) return null; + if (resp.statusCode != 200) { + Log().info("Get returned error code ${resp.statusCode}: ${resp.body}"); + return null; + } return convert.jsonDecode(resp.body) as Map; } @@ -79,12 +85,16 @@ class ProxmoxWebService { body: payload, ); - if (resp.statusCode != 200) return null; + if (resp.statusCode != 200) { + Log().info("Post returned error code ${resp.statusCode}: ${resp.body}"); + return null; + } return convert.jsonDecode(resp.body) as Map; } Future> listNodes() async { + Log().debug("Querying nodes"); List nodes = []; final resp = await _doGet("/api2/json/nodes"); @@ -98,6 +108,7 @@ class ProxmoxWebService { } Future> listVms(String node) async { + Log().debug("Querying vms"); List vms = []; final resp = await _doGet("/api2/json/nodes/$node/qemu"); if (resp == null) return []; @@ -113,8 +124,11 @@ class ProxmoxWebService { Future toggleVm(ProxmoxNode node, ProxmoxVm vm) async { if (!_authenticated) return false; + final isRunning = vm.status == "running"; + Log().info("toggling VM: ${isRunning ? "stopping" : "starting"}"); + final endpoint = - "/api2/json/nodes/${node.node}/qemu/${vm.vmid}/status/${vm.status == "running" ? "stop" : "start"}"; + "/api2/json/nodes/${node.node}/qemu/${vm.vmid}/status/${isRunning ? "stop" : "start"}"; final resp = await _doPost(endpoint, {}, debug: true); if (resp == null) return false; diff --git a/lib/src/screen_helper.dart b/lib/src/screen_helper.dart index 3bd7436..d5445d1 100644 --- a/lib/src/screen_helper.dart +++ b/lib/src/screen_helper.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:async/async.dart'; +import 'package:pi_dashboard/logger.dart'; const _off = "1"; const _on = "0"; @@ -8,6 +9,7 @@ const _on = "0"; bool _screenStatus = true; Future toggleScreen() async { + Log().info("Manually toggling screen light"); if (_screenStatus) { turnOffScreen(); } else { @@ -43,6 +45,7 @@ Widget screenActivator() { child: Material( color: Colors.black, child: InkWell(onTap: () async { + Log().info("Touch received: turning screen back on"); await turnOncreen(); }), ), diff --git a/lib/src/settings/settings_controller.dart b/lib/src/settings/settings_controller.dart index 4af9e1b..f29a34f 100644 --- a/lib/src/settings/settings_controller.dart +++ b/lib/src/settings/settings_controller.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:pi_dashboard/logger.dart'; import 'settings_service.dart'; @@ -38,14 +39,17 @@ class SettingsController with ChangeNotifier { Future setHostname(String newHostname) async { _hostname = newHostname; await _setKey("hostname", hostname); + Log().debug("Hostname update: $hostname"); } Future setUsername(String newUsername) async { _username = newUsername; await _setKey("username", username); + Log().debug("username update: $username"); } Future setPassword(String newPassword) async { _password = newPassword; await _setKey("password", password); + Log().debug("password update"); } /// Update and persist the ThemeMode based on the user's selection. Future updateThemeMode(ThemeMode? newThemeMode) async { diff --git a/lib/src/settings/settings_view.dart b/lib/src/settings/settings_view.dart index 51b6680..a4e9f72 100644 --- a/lib/src/settings/settings_view.dart +++ b/lib/src/settings/settings_view.dart @@ -7,13 +7,13 @@ import 'settings_controller.dart'; /// When a user changes a setting, the SettingsController is updated and /// Widgets that listen to the SettingsController are rebuilt. class SettingsView extends StatelessWidget { -const SettingsView({super.key, required this.controller}); + const SettingsView({super.key, required this.controller}); -static const routeName = '/settings'; + static const routeName = '/settings'; -final SettingsController controller; + final SettingsController controller; -@override + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( @@ -70,9 +70,8 @@ final SettingsController controller; onChanged: controller.setPassword, ), ], - ) - - ), + ), + ), ); } } diff --git a/pubspec.yaml b/pubspec.yaml index fd5e7e9..157db65 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dependencies: http: ^1.2.1 shared_preferences: async: ^2.11.0 + logger: ^2.2.0 flutter_gen: any dev_dependencies: