From b1e257ddd3e118cbbc3b078c02e1b8780c408802 Mon Sep 17 00:00:00 2001 From: Juan Gilsanz Polo Date: Sun, 16 Oct 2022 23:59:56 +0200 Subject: [PATCH] Added support for Home Assistant servers --- lib/l10n/app_en.arb | 4 +- lib/l10n/app_es.arb | 4 +- lib/models/app_log.dart | 5 ++ lib/models/server.dart | 4 +- lib/providers/servers_provider.dart | 7 ++- lib/services/database.dart | 20 +++++- lib/services/http_requests.dart | 59 ++++++++++++++++- lib/widgets/add_server_modal.dart | 73 ++++++++++++++++++++-- lib/widgets/servers_list/servers_list.dart | 4 +- 9 files changed, 165 insertions(+), 15 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6f0d536..7a16126 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -408,5 +408,7 @@ "logsConfigNotUpdated": "Logs settings couldn't be updated", "deletingLogs": "Clearing logs...", "logsCleared": "Logs cleared successfully", - "logsNotCleared": "Logs could not be cleared" + "logsNotCleared": "Logs could not be cleared", + "runningHomeAssistant": "Running on Home Assistant", + "serverError": "Server error" } \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index c951daf..8590a85 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -408,5 +408,7 @@ "logsConfigNotUpdated": "La configuración de registros no ha podido ser actualizada", "deletingLogs": "Borrando registros...", "logsCleared": "Registros borrados correctamente", - "logsNotCleared": "No se han podido borrar los registros" + "logsNotCleared": "No se han podido borrar los registros", + "runningHomeAssistant": "Ejecutando en Home Assistant", + "serverError": "Error del servidor" } \ No newline at end of file diff --git a/lib/models/app_log.dart b/lib/models/app_log.dart index 2ad5ad8..9c5c143 100644 --- a/lib/models/app_log.dart +++ b/lib/models/app_log.dart @@ -22,4 +22,9 @@ class AppLog { 'resBody': resBody.toString() }; } + + @override + String toString() { + return toMap().toString(); + } } \ No newline at end of file diff --git a/lib/models/server.dart b/lib/models/server.dart index 1929b84..162cef7 100644 --- a/lib/models/server.dart +++ b/lib/models/server.dart @@ -9,6 +9,7 @@ class Server { String password; bool defaultServer; String authToken; + bool runningOnHa; Server({ required this.id, @@ -20,6 +21,7 @@ class Server { required this.user, required this.password, required this.defaultServer, - required this.authToken + required this.authToken, + required this.runningOnHa, }); } \ No newline at end of file diff --git a/lib/providers/servers_provider.dart b/lib/providers/servers_provider.dart index 23f0af4..bac7da2 100644 --- a/lib/providers/servers_provider.dart +++ b/lib/providers/servers_provider.dart @@ -362,7 +362,7 @@ class ServersProvider with ChangeNotifier { try { return await _dbInstance!.transaction((txn) async { await txn.rawInsert( - 'INSERT INTO servers (id, name, connectionMethod, domain, path, port, user, password, defaultServer, authToken) VALUES ("${server.id}", "${server.name}", "${server.connectionMethod}", "${server.domain}", ${server.path != null ? "${server.path}" : null}, ${server.port}, "${server.user}", "${server.password}", 0, "${server.authToken}")', + 'INSERT INTO servers (id, name, connectionMethod, domain, path, port, user, password, defaultServer, authToken, runningOnHa) VALUES ("${server.id}", "${server.name}", "${server.connectionMethod}", "${server.domain}", ${server.path != null ? "${server.path}" : null}, ${server.port}, "${server.user}", "${server.password}", 0, "${server.authToken}", ${convertFromBoolToInt(server.runningOnHa)})', ); return true; }); @@ -375,7 +375,7 @@ class ServersProvider with ChangeNotifier { try { return await _dbInstance!.transaction((txn) async { await txn.rawUpdate( - 'UPDATE servers SET name = "${server.name}", connectionMethod = "${server.connectionMethod}", domain = "${server.domain}", path = ${server.path != null ? "${server.path}" : null}, port = ${server.port}, user = "${server.user}", password = "${server.password}", authToken = "${server.authToken}" WHERE id = "${server.id}"', + 'UPDATE servers SET name = "${server.name}", connectionMethod = "${server.connectionMethod}", domain = "${server.domain}", path = ${server.path != null ? "${server.path}" : null}, port = ${server.port}, user = "${server.user}", password = "${server.password}", authToken = "${server.authToken}", runningOnHa = ${convertFromBoolToInt(server.runningOnHa)} WHERE id = "${server.id}"', ); return true; }); @@ -426,7 +426,8 @@ class ServersProvider with ChangeNotifier { user: server['user'], password: server['password'], defaultServer: convertFromIntToBool(server['defaultServer'])!, - authToken: server['authToken'] + authToken: server['authToken'], + runningOnHa: convertFromIntToBool(server['runningOnHa'])!, ); _serversList.add(serverObj); if (convertFromIntToBool(server['defaultServer']) == true) { diff --git a/lib/services/database.dart b/lib/services/database.dart index e7442b8..1cafa1d 100644 --- a/lib/services/database.dart +++ b/lib/services/database.dart @@ -26,11 +26,22 @@ Future> loadDb() async { }); } + Future upgradeDbToV4(Database db) async { + await db.execute("ALTER TABLE servers ADD COLUMN runningOnHa INTEGER"); + await db.execute("UPDATE servers SET runningOnHa = 0"); + + await db.transaction((txn) async{ + await txn.rawQuery( + 'SELECT * FROM servers', + ); + }); + } + Database db = await openDatabase( 'adguard_home_manager.db', - version: 3, + version: 4, onCreate: (Database db, int version) async { - await db.execute("CREATE TABLE servers (id TEXT PRIMARY KEY, name TEXT, connectionMethod TEXT, domain TEXT, path TEXT, port INTEGER, user TEXT, password TEXT, defaultServer INTEGER, authToken TEXT)"); + await db.execute("CREATE TABLE servers (id TEXT PRIMARY KEY, name TEXT, connectionMethod TEXT, domain TEXT, path TEXT, port INTEGER, user TEXT, password TEXT, defaultServer INTEGER, authToken TEXT, runningOnHa INTEGER)"); await db.execute("CREATE TABLE appConfig (theme NUMERIC, overrideSslCheck NUMERIC, hideZeroValues NUMERIC)"); await db.execute("INSERT INTO appConfig (theme, overrideSslCheck, hideZeroValues) VALUES (0, 0, 0)"); }, @@ -38,9 +49,14 @@ Future> loadDb() async { if (oldVersion == 1) { await upgradeDbToV2(db); await upgradeDbToV3(db); + await upgradeDbToV4(db); } if (oldVersion == 2) { await upgradeDbToV3(db); + await upgradeDbToV4(db); + } + if (oldVersion == 3) { + await upgradeDbToV4(db); } }, onOpen: (Database db) async { diff --git a/lib/services/http_requests.dart b/lib/services/http_requests.dart index 71c39bb..05e162c 100644 --- a/lib/services/http_requests.dart +++ b/lib/services/http_requests.dart @@ -29,7 +29,7 @@ Future> apiRequest({ HttpClient httpClient = HttpClient(); if (method == 'get') { HttpClientRequest request = await httpClient.getUrl(Uri.parse("${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}/control$urlPath")); - request.headers.set('authorization', 'Basic ${server.authToken}'); + request.headers.set('Authorization', 'Basic ${server.authToken}'); HttpClientResponse response = overrideTimeout == true ? await request.close() : await request.close().timeout(const Duration(seconds: 10)); @@ -54,7 +54,7 @@ Future> apiRequest({ } else if (method == 'post') { HttpClientRequest request = await httpClient.postUrl(Uri.parse("${server.connectionMethod}://${server.domain}${server.path ?? ""}${server.port != null ? ':${server.port}' : ""}/control$urlPath")); - request.headers.set('authorization', 'Basic ${server.authToken}'); + request.headers.set('Authorization', 'Basic ${server.authToken}'); request.headers.set('content-type', 'application/json'); request.add(utf8.encode(json.encode(body))); HttpClientResponse response = overrideTimeout == true @@ -165,6 +165,18 @@ Future login(Server server) async { ) }; } + else if (result['statusCode'] == 500) { + return { + 'result': 'server_error', + 'log': AppLog( + type: 'login', + dateTime: DateTime.now(), + message: 'server_error', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } else { return { 'result': 'error', @@ -183,6 +195,49 @@ Future login(Server server) async { } } +Future loginHA(Server server) async { + print(server.authToken); + final result = await apiRequest( + server: server, + method: 'get', + urlPath: '/status', + type: 'login_ha' + ); + + if (result['hasResponse'] == true) { + if (result['statusCode'] == 200) { + return {'result': 'success'}; + } + else if (result['statusCode'] == 401) { + return { + 'result': 'invalid_username_password', + 'log': AppLog( + type: 'login', + dateTime: DateTime.now(), + message: 'invalid_username_password', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } + else { + return { + 'result': 'error', + 'log': AppLog( + type: 'login_ha', + dateTime: DateTime.now(), + message: 'error_code_not_expected', + statusCode: result['statusCode'].toString(), + resBody: result['body'] + ) + }; + } + } + else { + return result; + } +} + Future getServerStatus(Server server) async { final result = await Future.wait([ apiRequest(server: server, method: 'get', urlPath: '/stats', type: 'server_status'), diff --git a/lib/widgets/add_server_modal.dart b/lib/widgets/add_server_modal.dart index 6b4f733..d5805f0 100644 --- a/lib/widgets/add_server_modal.dart +++ b/lib/widgets/add_server_modal.dart @@ -51,6 +51,8 @@ class _AddServerModalState extends State { bool defaultServer = false; + bool homeAssistant = false; + bool allDataValid = false; bool isConnecting = false; @@ -254,11 +256,19 @@ class _AddServerModalState extends State { user: userController.text, password: passwordController.text, defaultServer: defaultServer, - authToken: '' + authToken: homeAssistant == true + ? encodeBase64UserPass(userController.text, passwordController.text) + : '', + runningOnHa: homeAssistant ); setState(() => isConnecting = true); - final result = await login(serverObj); + + final result = homeAssistant == true + ? await loginHA(serverObj) + : await login(serverObj); + setState(() => isConnecting = false); + if (result['result'] == 'success') { serverObj.authToken = encodeBase64UserPass(serverObj.user, serverObj.password); final serverCreated = await serversProvider.createServer(serverObj); @@ -320,6 +330,15 @@ class _AddServerModalState extends State { ) ); } + else if (result['result'] == 'server_error') { + appConfigProvider.addLog(result['log']); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.serverError), + backgroundColor: Colors.red, + ) + ); + } else { appConfigProvider.addLog(result['log']); ScaffoldMessenger.of(context).showSnackBar( @@ -341,9 +360,16 @@ class _AddServerModalState extends State { user: userController.text, password: passwordController.text, defaultServer: defaultServer, - authToken: '' + authToken: homeAssistant == true + ? encodeBase64UserPass(userController.text, passwordController.text) + : '', + runningOnHa: homeAssistant ); - final result = await login(serverObj); + + final result = homeAssistant == true + ? await loginHA(serverObj) + : await login(serverObj); + if (result['result'] == 'success') { serverObj.authToken = encodeBase64UserPass(serverObj.user, serverObj.password); final serverSaved = await serversProvider.editServer(serverObj); @@ -395,6 +421,15 @@ class _AddServerModalState extends State { ) ); } + else if (result['result'] == 'server_error') { + appConfigProvider.addLog(result['log']); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(AppLocalizations.of(context)!.serverError), + backgroundColor: Colors.red, + ) + ); + } else { appConfigProvider.addLog(result['log']); ScaffoldMessenger.of(context).showSnackBar( @@ -566,6 +601,36 @@ class _AddServerModalState extends State { ), ), const SizedBox(height: 20), + Material( + color: Colors.transparent, + child: InkWell( + onTap: widget.server == null + ? () => setState(() => homeAssistant = !homeAssistant) + : null, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + AppLocalizations.of(context)!.runningHomeAssistant, + style: const TextStyle( + fontSize: 15, + ), + ), + Switch( + value: homeAssistant, + onChanged: widget.server == null + ? (value) => setState(() => homeAssistant = value) + : null, + activeColor: Theme.of(context).primaryColor, + ) + ], + ), + ), + ), + ), + const SizedBox(height: 20), ], ), ), diff --git a/lib/widgets/servers_list/servers_list.dart b/lib/widgets/servers_list/servers_list.dart index 1b9d49a..0413cc5 100644 --- a/lib/widgets/servers_list/servers_list.dart +++ b/lib/widgets/servers_list/servers_list.dart @@ -60,7 +60,9 @@ class ServersList extends StatelessWidget { final ProcessModal process = ProcessModal(context: context); process.open(AppLocalizations.of(context)!.connecting); - final result = await login(server); + final result = server.runningOnHa == true + ? await loginHA(server) + : await login(server); if (result['result'] == 'success') { serversProvider.setSelectedServer(server);