mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
296 lines
9.4 KiB
Dart
296 lines
9.4 KiB
Dart
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<ConnectionStatsPage> createState() => _ConnectionStatsPageState();
|
|
}
|
|
|
|
class _ConnectionStatsPageState extends State<ConnectionStatsPage> {
|
|
List<ServerConnectionStats> _serverStats = [];
|
|
bool _isLoading = true;
|
|
|
|
@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,
|
|
),
|
|
],
|
|
),
|
|
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(l10n.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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
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,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|