From 04b47a385af68b77dbd0454bf76292d764b9ce36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sat, 7 Mar 2026 02:06:45 +0800 Subject: [PATCH] fix(server): remove connection stats feature (#1063) --- lib/data/provider/server/all.dart | 4 - lib/data/provider/server/single.dart | 34 +- lib/data/res/store.dart | 1 + lib/view/page/server/connection_stats.dart | 341 --------------------- lib/view/page/server/tab/tab.dart | 1 - lib/view/page/server/tab/top_bar.dart | 5 +- lib/view/page/setting/entries/server.dart | 13 - lib/view/page/setting/entry.dart | 1 - 8 files changed, 3 insertions(+), 397 deletions(-) delete mode 100644 lib/view/page/server/connection_stats.dart diff --git a/lib/data/provider/server/all.dart b/lib/data/provider/server/all.dart index bbfa6f03..3475df93 100644 --- a/lib/data/provider/server/all.dart +++ b/lib/data/provider/server/all.dart @@ -225,9 +225,6 @@ class ServersNotifier extends _$ServersNotifier { Stores.setting.serverOrder.put(newOrder); Stores.server.delete(id); - // Remove connection stats when server is deleted - Stores.connectionStats.clearServerStats(id); - // Remove SSH session when server is deleted final sessionId = 'ssh_$id'; TermSessionManager.remove(sessionId); @@ -246,7 +243,6 @@ class ServersNotifier extends _$ServersNotifier { Stores.setting.serverOrder.put([]); Stores.server.clear(); - Stores.connectionStats.clearAll(); bakSync.sync(milliDelay: 1000); } diff --git a/lib/data/provider/server/single.dart b/lib/data/provider/server/single.dart index 5b7df3bb..28ef3f9b 100644 --- a/lib/data/provider/server/single.dart +++ b/lib/data/provider/server/single.dart @@ -13,7 +13,6 @@ import 'package:server_box/data/helper/system_detector.dart'; import 'package:server_box/data/model/app/error.dart'; import 'package:server_box/data/model/app/scripts/script_consts.dart'; import 'package:server_box/data/model/app/scripts/shell_func.dart'; -import 'package:server_box/data/model/server/connection_stat.dart'; import 'package:server_box/data/model/server/server.dart'; import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_status_update_req.dart'; @@ -141,15 +140,6 @@ class ServerNotifier extends _$ServerNotifier { Loggers.app.info('Jump to ${spi.name} in $spentTime ms.'); } - // Record successful connection - Stores.connectionStats.recordConnection(ConnectionStat( - serverId: spi.id, - serverName: spi.name, - timestamp: time1, - result: ConnectionResult.success, - durationMs: spentTime, - )); - final sessionId = 'ssh_${spi.id}'; TermSessionManager.add( id: sessionId, @@ -161,29 +151,7 @@ class ServerNotifier extends _$ServerNotifier { TermSessionManager.setActive(sessionId, hasTerminal: false); } catch (e) { TryLimiter.inc(sid); - - // Determine connection failure type - ConnectionResult failureResult; - if (e.toString().contains('timeout') || e.toString().contains('Timeout')) { - failureResult = ConnectionResult.timeout; - } else if (e.toString().contains('auth') || e.toString().contains('Authentication')) { - failureResult = ConnectionResult.authFailed; - } else if (e.toString().contains('network') || e.toString().contains('Network')) { - failureResult = ConnectionResult.networkError; - } else { - failureResult = ConnectionResult.unknownError; - } - - // Record failed connection - Stores.connectionStats.recordConnection(ConnectionStat( - serverId: spi.id, - serverName: spi.name, - timestamp: DateTime.now(), - result: failureResult, - errorMessage: e.toString(), - durationMs: 0, - )); - + final newStatus = state.status..err = SSHErr(type: SSHErrType.connect, message: e.toString()); updateStatus(newStatus); updateConnection(ServerConn.failed); diff --git a/lib/data/res/store.dart b/lib/data/res/store.dart index e35fbc6f..8954f92a 100644 --- a/lib/data/res/store.dart +++ b/lib/data/res/store.dart @@ -17,6 +17,7 @@ abstract final class Stores { static PrivateKeyStore get key => getIt(); static SnippetStore get snippet => getIt(); static HistoryStore get history => getIt(); + // Keep the legacy box registered so existing connection stats DB files remain intact. static ConnectionStatsStore get connectionStats => getIt(); /// All stores that need backup diff --git a/lib/view/page/server/connection_stats.dart b/lib/view/page/server/connection_stats.dart deleted file mode 100644 index 697ce236..00000000 --- a/lib/view/page/server/connection_stats.dart +++ /dev/null @@ -1,341 +0,0 @@ -import 'dart:io'; - -import 'package:fl_lib/fl_lib.dart'; -import 'package:flutter/material.dart'; -import 'package:server_box/core/extension/context/locale.dart'; -import 'package:server_box/data/model/server/connection_stat.dart'; -import 'package:server_box/data/res/store.dart'; - -class ConnectionStatsPage extends StatefulWidget { - const ConnectionStatsPage({super.key}); - - static const route = AppRouteNoArg(page: ConnectionStatsPage.new, path: '/server/conn_stats'); - - @override - State createState() => _ConnectionStatsPageState(); -} - -class _ConnectionStatsPageState extends State { - List _serverStats = []; - bool _isLoading = true; - bool _isCompacting = false; - - @override - void initState() { - super.initState(); - _loadStats(); - } - - void _loadStats() { - setState(() { - _isLoading = true; - }); - - final stats = Stores.connectionStats.getAllServerStats(); - setState(() { - _serverStats = stats; - _isLoading = false; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: CustomAppBar( - title: Text(l10n.connectionStats), - actions: [ - IconButton(onPressed: _loadStats, icon: const Icon(Icons.refresh), tooltip: libL10n.refresh), - IconButton( - onPressed: _showClearAllDialog, - icon: const Icon(Icons.clear_all, color: Colors.red), - tooltip: libL10n.clear, - ), - IconButton( - onPressed: _isCompacting ? null : _showCompactDialog, - icon: _isCompacting - ? SizedLoading.small - : const Icon(Icons.compress), - tooltip: l10n.compactDatabase, - ), - ], - ), - body: _buildBody, - ); - } - - Widget get _buildBody { - if (_isLoading) { - return const Center(child: SizedLoading.large); - } - if (_serverStats.isEmpty) { - return Center(child: Text(l10n.noConnectionStatsData)); - } - - return ListView.builder( - itemCount: _serverStats.length, - itemBuilder: (context, index) { - final stats = _serverStats[index]; - return _buildServerStatsCard(stats); - }, - ); - } - - Widget _buildServerStatsCard(ServerConnectionStats stats) { - final successRate = stats.totalAttempts == 0 ? 'N/A' : '${(stats.successRate * 100).toStringAsFixed(1)}%'; - final lastSuccessTime = stats.lastSuccessTime; - final lastFailureTime = stats.lastFailureTime; - - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - stats.serverName, - style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - ), - Text( - '${libL10n.success}: $successRate', - style: TextStyle( - fontSize: 16, - color: stats.successRate >= 0.8 - ? Colors.green - : stats.successRate >= 0.5 - ? Colors.orange - : Colors.red, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildStatItem(libL10n.totalAttempts, stats.totalAttempts.toString(), Icons.all_inclusive), - _buildStatItem( - libL10n.success, - stats.successCount.toString(), - Icons.check_circle, - Colors.green, - ), - _buildStatItem(libL10n.fail, stats.failureCount.toString(), Icons.error, Colors.red), - ], - ), - if (lastSuccessTime != null || lastFailureTime != null) ...[ - const SizedBox(height: 16), - const Divider(), - const SizedBox(height: 8), - if (lastSuccessTime != null) - _buildTimeItem(l10n.lastSuccess, lastSuccessTime, Icons.check_circle, Colors.green), - if (lastFailureTime != null) - _buildTimeItem(l10n.lastFailure, lastFailureTime, Icons.error, Colors.red), - ], - const SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(l10n.recentConnections, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), - TextButton(onPressed: () => _showServerDetailsDialog(stats), child: Text(l10n.viewDetails)), - ], - ), - const SizedBox(height: 8), - ...stats.recentConnections.take(3).map(_buildConnectionItem), - ], - ), - ).cardx; - } - - Widget _buildStatItem(String label, String value, IconData icon, [Color? color]) { - return Column( - children: [ - Icon(icon, size: 24, color: color ?? Colors.grey), - const SizedBox(height: 4), - Text( - value, - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: color), - ), - Text(label, style: TextStyle(fontSize: 12, color: Colors.grey[600])), - ], - ); - } - - Widget _buildTimeItem(String label, DateTime time, IconData icon, Color color) { - final timeStr = time.simple(); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - children: [ - Icon(icon, size: 16, color: color), - UIs.width7, - Text('$label: ', style: TextStyle(fontSize: 12, color: Colors.grey[600])), - Text(timeStr, style: const TextStyle(fontSize: 12)), - ], - ), - ); - } - - Widget _buildConnectionItem(ConnectionStat stat) { - final timeStr = stat.timestamp.simple(); - final isSuccess = stat.result.isSuccess; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row( - children: [ - Icon( - isSuccess ? Icons.check_circle : Icons.error, - size: 16, - color: isSuccess ? Colors.green : Colors.red, - ), - UIs.width7, - Text(timeStr, style: const TextStyle(fontSize: 12)), - UIs.width7, - Expanded( - child: Text( - isSuccess ? '${libL10n.success} (${stat.durationMs}ms)' : stat.result.displayName, - style: TextStyle(fontSize: 12, color: isSuccess ? Colors.green : Colors.red), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - ); - } - - void _showCompactDialog() { - final path = '${Paths.doc}${Pfs.seperator}connection_stats_enc.hive'; - final file = File(path); - final oldSize = file.existsSync() ? file.lengthSync() : 0; - final sizeStr = oldSize < 1000 ? '$oldSize B' : oldSize < 1000 * 1000 ? '${(oldSize / 1000).toStringAsFixed(1)} KB' : '${(oldSize / (1000 * 1000)).toStringAsFixed(1)} MB'; - - context.showRoundDialog( - title: l10n.compactDatabase, - child: Text(l10n.compactDatabaseContent(sizeStr)), - actions: [ - TextButton(onPressed: context.pop, child: Text(libL10n.cancel)), - TextButton( - onPressed: () async { - context.pop(); - setState(() => _isCompacting = true); - try { - await Stores.connectionStats.compact(); - final newSize = file.existsSync() ? file.lengthSync() : 0; - final newSizeStr = newSize < 1000 ? '$newSize B' : newSize < 1000 * 1000 ? '${(newSize / 1000).toStringAsFixed(1)} KB' : '${(newSize / (1000 * 1000)).toStringAsFixed(1)} MB'; - if (mounted) { - setState(() => _isCompacting = false); - context.showSnackBar('${libL10n.success}: $sizeStr -> $newSizeStr'); - } - } catch (e) { - if (mounted) { - setState(() => _isCompacting = false); - context.showSnackBar('${libL10n.error}: $e'); - } - } - }, - child: Text(libL10n.confirm), - ), - ], - ); - } -} - -extension on _ConnectionStatsPageState { - void _showServerDetailsDialog(ServerConnectionStats stats) { - context.showRoundDialog( - title: '${stats.serverName} - ${l10n.connectionDetails}', - child: SizedBox( - width: double.maxFinite, - height: MediaQuery.sizeOf(context).height * 0.7, - child: ListView.separated( - itemCount: stats.recentConnections.length, - separatorBuilder: (context, index) => const Divider(), - itemBuilder: (context, index) { - final stat = stats.recentConnections[index]; - final timeStr = stat.timestamp.simple(); - final isSuccess = stat.result.isSuccess; - - return ListTile( - contentPadding: EdgeInsets.zero, - leading: Icon( - isSuccess ? Icons.check_circle : Icons.error, - color: isSuccess ? Colors.green : Colors.red, - ), - title: Text(timeStr), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - isSuccess - ? '${libL10n.success} (${stat.durationMs}ms)' - : '${libL10n.fail}: ${stat.result.displayName}', - style: TextStyle(color: isSuccess ? Colors.green : Colors.red), - ), - if (!isSuccess && stat.errorMessage.isNotEmpty) - Text( - stat.errorMessage, - style: const TextStyle(fontSize: 11, color: Colors.grey), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ); - }, - ), - ), - actions: [ - TextButton(onPressed: context.pop, child: Text(libL10n.close)), - TextButton( - onPressed: () { - Navigator.of(context).pop(); - _showClearServerStatsDialog(stats); - }, - child: Text(l10n.clearThisServerStats, style: TextStyle(color: Colors.red)), - ), - ], - ); - } - - void _showClearAllDialog() { - context.showRoundDialog( - title: l10n.clearAllStatsTitle, - child: Text(l10n.clearAllStatsContent), - actions: [ - TextButton(onPressed: context.pop, child: Text(libL10n.cancel)), - CountDownBtn( - onTap: () { - context.pop(); - Stores.connectionStats.clearAll(); - _loadStats(); - }, - text: libL10n.ok, - afterColor: Colors.red, - ), - ], - ); - } - - void _showClearServerStatsDialog(ServerConnectionStats stats) { - context.showRoundDialog( - title: l10n.clearServerStatsTitle(stats.serverName), - child: Text(l10n.clearServerStatsContent(stats.serverName)), - actions: [ - TextButton(onPressed: context.pop, child: Text(libL10n.cancel)), - CountDownBtn( - onTap: () { - context.pop(); - Stores.connectionStats.clearServerStats(stats.serverId); - _loadStats(); - }, - text: libL10n.ok, - afterColor: Colors.red, - ), - ], - ); - } -} diff --git a/lib/view/page/server/tab/tab.dart b/lib/view/page/server/tab/tab.dart index 6779b18c..c1cb127e 100644 --- a/lib/view/page/server/tab/tab.dart +++ b/lib/view/page/server/tab/tab.dart @@ -21,7 +21,6 @@ import 'package:server_box/data/provider/server/all.dart'; import 'package:server_box/data/provider/server/single.dart'; import 'package:server_box/data/res/build_data.dart'; import 'package:server_box/data/res/store.dart'; -import 'package:server_box/view/page/server/connection_stats.dart'; import 'package:server_box/view/page/server/detail/view.dart'; import 'package:server_box/view/page/server/edit/edit.dart'; import 'package:server_box/view/page/setting/entry.dart'; diff --git a/lib/view/page/server/tab/top_bar.dart b/lib/view/page/server/tab/top_bar.dart index 3dd58aab..523f1318 100644 --- a/lib/view/page/server/tab/top_bar.dart +++ b/lib/view/page/server/tab/top_bar.dart @@ -42,10 +42,7 @@ final class _TopBar extends ConsumerWidget implements PreferredSizeWidget { } final total = order.length; final connectionText = '$connected/$total ${context.libL10n.conn}'; - leading = InkWell( - onTap: () => ConnectionStatsPage.route.go(context), - child: Text(connectionText, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)), - ); + leading = Text(connectionText, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)); } return Padding( diff --git a/lib/view/page/setting/entries/server.dart b/lib/view/page/setting/entries/server.dart index a4959017..3a1c5c4b 100644 --- a/lib/view/page/setting/entries/server.dart +++ b/lib/view/page/setting/entries/server.dart @@ -9,7 +9,6 @@ extension _Server on _AppSettingsPageState { _buildNetViewType(), _buildServerSeq(), _buildServerDetailCardSeq(), - _buildConnectionStats(), _buildDeleteServers(), _buildCpuView(), _buildServerMore(), @@ -39,18 +38,6 @@ extension _Server on _AppSettingsPageState { ); } - Widget _buildConnectionStats() { - return ListTile( - leading: const Icon(Icons.analytics, size: _kIconSize), - title: Text(l10n.connectionStats), - subtitle: Text(l10n.connectionStatsDesc), - trailing: const Icon(Icons.keyboard_arrow_right), - onTap: () { - ConnectionStatsPage.route.go(context); - }, - ); - } - Widget _buildDeleteServers() { return ListTile( title: Text(l10n.deleteServers), diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index a19ebfcd..99e6e921 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -18,7 +18,6 @@ import 'package:server_box/data/store/setting.dart'; import 'package:server_box/generated/l10n/l10n.dart'; import 'package:server_box/view/page/backup.dart'; import 'package:server_box/view/page/private_key/list.dart'; -import 'package:server_box/view/page/server/connection_stats.dart'; import 'package:server_box/view/page/setting/entries/home_tabs.dart'; import 'package:server_box/view/page/setting/platform/ios.dart'; import 'package:server_box/view/page/setting/platform/platform_pub.dart';