Compare commits

...

3 Commits

Author SHA1 Message Date
Junyuan Feng
7fb8c88ab8 detail page display memory exact value 2021-12-31 18:51:33 +08:00
Junyuan Feng
f480c49f1f scroll server connection failed info 2021-12-31 17:55:07 +08:00
Junyuan Feng
f7558d6beb can manually refresh when updateInterval==0 2021-12-31 17:44:17 +08:00
10 changed files with 131 additions and 53 deletions

View File

@@ -38,9 +38,9 @@ A new Flutter project which provide a chart view to display server status data.
- [x] Private key store - [x] Private key store
- [x] Server status detail page - [x] Server status detail page
- [x] Theme switch - [x] Theme switch
- [ ] Execute snippet - [x] Execute snippet
- [ ] Migrate from `ssh2` to `dartssh2` - [ ] Migrate from `ssh2` to `dartssh2`
- [ ] Desktop support. - [ ] Desktop support
## Build ## Build
Please use `make.dart` to build. Please use `make.dart` to build.

View File

@@ -5,6 +5,8 @@ PODS:
- GZ-NMSSH (4.1.5) - GZ-NMSSH (4.1.5)
- path_provider (0.0.1): - path_provider (0.0.1):
- Flutter - Flutter
- r_upgrade (0.0.1):
- Flutter
- ssh2 (2.2.3): - ssh2 (2.2.3):
- Flutter - Flutter
- GZ-NMSSH (~> 4.1.5) - GZ-NMSSH (~> 4.1.5)
@@ -15,6 +17,7 @@ DEPENDENCIES:
- countly_flutter (from `.symlinks/plugins/countly_flutter/ios`) - countly_flutter (from `.symlinks/plugins/countly_flutter/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`)
- r_upgrade (from `.symlinks/plugins/r_upgrade/ios`)
- ssh2 (from `.symlinks/plugins/ssh2/ios`) - ssh2 (from `.symlinks/plugins/ssh2/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher (from `.symlinks/plugins/url_launcher/ios`)
@@ -29,6 +32,8 @@ EXTERNAL SOURCES:
:path: Flutter :path: Flutter
path_provider: path_provider:
:path: ".symlinks/plugins/path_provider/ios" :path: ".symlinks/plugins/path_provider/ios"
r_upgrade:
:path: ".symlinks/plugins/r_upgrade/ios"
ssh2: ssh2:
:path: ".symlinks/plugins/ssh2/ios" :path: ".symlinks/plugins/ssh2/ios"
url_launcher: url_launcher:
@@ -39,6 +44,7 @@ SPEC CHECKSUMS:
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
GZ-NMSSH: d749f8ae2fd0094b953cd1d5abd8e0cab3c93f8d GZ-NMSSH: d749f8ae2fd0094b953cd1d5abd8e0cab3c93f8d
path_provider: d1e9807085df1f9cc9318206cd649dc0b76be3de path_provider: d1e9807085df1f9cc9318206cd649dc0b76be3de
r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114
ssh2: 74165efc99417a075ecafd52caf93edadfb5eb60 ssh2: 74165efc99417a075ecafd52caf93edadfb5eb60
url_launcher: b6e016d912f04be9f5bf6e8e82dc599b7ba59649 url_launcher: b6e016d912f04be9f5bf6e8e82dc599b7ba59649

View File

@@ -40,5 +40,6 @@ class ServerStatus {
String? failedInfo; String? failedInfo;
ServerStatus(this.cpu2Status, this.memory, this.sysVer, this.uptime, ServerStatus(this.cpu2Status, this.memory, this.sysVer, this.uptime,
this.disk, this.tcp, this.netSpeed, {this.failedInfo}); this.disk, this.tcp, this.netSpeed,
{this.failedInfo});
} }

View File

@@ -2,8 +2,9 @@
class BuildData { class BuildData {
static const String name = "ToolBox"; static const String name = "ToolBox";
static const int build = 64; static const int build = 65;
static const String engine = "Flutter 2.5.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 18116933e7 (5 weeks ago) • 2021-10-15 10:46:35 -0700\nEngine • revision d3ea636dc5\nTools • Dart 2.14.4\n"; static const String engine =
static const String buildAt = "2021-11-21 19:42:23.223010"; "Flutter 2.8.1 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 77d935af4d (2 weeks ago) • 2021-12-16 08:37:33 -0800\nEngine • revision 890a5fca2e\nTools • Dart 2.15.1\n";
static const int modifications = 2; static const String buildAt = "2021-12-31 15:55:47.350456";
static const int modifications = 7;
} }

View File

@@ -184,6 +184,16 @@ class _ServerDetailPageState extends State<ServerDetailPage>
)); ));
} }
String convertMB(int mb) {
const suffix = ['MB', 'GB', 'TB'];
double value = mb.toDouble();
int squareTimes = 0;
for (; value / 1024 > 1 && squareTimes < 3; squareTimes++) {
value /= 1024;
}
return '${value.toStringAsFixed(1)} ${suffix[squareTimes]}';
}
Widget _buildMemView(ServerStatus ss) { Widget _buildMemView(ServerStatus ss) {
final pColor = primaryColor; final pColor = primaryColor;
final used = ss.memory.used / ss.memory.total; final used = ss.memory.used / ss.memory.total;
@@ -197,9 +207,11 @@ class _ServerDetailPageState extends State<ServerDetailPage>
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
_buildMemExplain('Used', pColor), _buildMemExplain(convertMB(ss.memory.used), pColor),
_buildMemExplain('Cache', pColor.withAlpha(77)), _buildMemExplain(
_buildMemExplain('Avail', progressColor.resolve(context)) convertMB(ss.memory.cache), pColor.withAlpha(77)),
_buildMemExplain(
convertMB(ss.memory.total - ss.memory.avail), progressColor.resolve(context))
], ],
), ),
const SizedBox( const SizedBox(

View File

@@ -1,15 +1,17 @@
import 'package:after_layout/after_layout.dart'; import 'package:after_layout/after_layout.dart';
import 'package:circle_chart/circle_chart.dart'; import 'package:circle_chart/circle_chart.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:marquee/marquee.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:toolbox/core/route.dart'; import 'package:toolbox/core/route.dart';
import 'package:toolbox/data/model/server/server.dart'; import 'package:toolbox/data/model/server/server.dart';
import 'package:toolbox/data/model/server/server_connection_state.dart'; import 'package:toolbox/data/model/server/server_connection_state.dart';
import 'package:toolbox/data/model/server/server_status.dart'; import 'package:toolbox/data/model/server/server_status.dart';
import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/provider/server.dart';
import 'package:toolbox/data/res/color.dart';
import 'package:toolbox/data/store/setting.dart'; import 'package:toolbox/data/store/setting.dart';
import 'package:toolbox/locator.dart'; import 'package:toolbox/locator.dart';
import 'package:toolbox/view/page/server/detail.dart'; import 'package:toolbox/view/page/server/detail.dart';
@@ -27,6 +29,7 @@ class _ServerPageState extends State<ServerPage>
late MediaQueryData _media; late MediaQueryData _media;
late ThemeData _theme; late ThemeData _theme;
late Color _primaryColor; late Color _primaryColor;
late RefreshController _refreshController;
late ServerProvider _serverProvider; late ServerProvider _serverProvider;
@@ -34,6 +37,7 @@ class _ServerPageState extends State<ServerPage>
void initState() { void initState() {
super.initState(); super.initState();
_serverProvider = locator<ServerProvider>(); _serverProvider = locator<ServerProvider>();
_refreshController = RefreshController();
} }
@override @override
@@ -41,41 +45,53 @@ class _ServerPageState extends State<ServerPage>
super.didChangeDependencies(); super.didChangeDependencies();
_media = MediaQuery.of(context); _media = MediaQuery.of(context);
_theme = Theme.of(context); _theme = Theme.of(context);
_primaryColor = Color(locator<SettingStore>().primaryColor.fetch()!); _primaryColor = primaryColor;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( final autoUpdate =
body: Consumer<ServerProvider>(builder: (_, pro, __) { locator<SettingStore>().serverStatusUpdateInterval.fetch() != 0;
if (pro.servers.isEmpty) { final child = Consumer<ServerProvider>(builder: (_, pro, __) {
return const Center( if (pro.servers.isEmpty) {
child: Text( return const Center(
'There is no server.\nClick the fab to add one.', child: Text(
textAlign: TextAlign.center, 'There is no server.\nClick the fab to add one.',
), textAlign: TextAlign.center,
); ),
}
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 7),
child: AnimationLimiter(
child: Column(
children: AnimationConfiguration.toStaggeredList(
duration: const Duration(milliseconds: 377),
childAnimationBuilder: (widget) => SlideAnimation(
verticalOffset: 50.0,
child: FadeInAnimation(
child: widget,
),
),
children: [
const SizedBox(height: 13),
...pro.servers.map((e) => _buildEachServerCard(e))
],
))),
); );
}), }
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 7),
child: AnimationLimiter(
child: Column(
children: AnimationConfiguration.toStaggeredList(
duration: const Duration(milliseconds: 777),
childAnimationBuilder: (widget) => SlideAnimation(
verticalOffset: 77.0,
child: FadeInAnimation(
child: widget,
),
),
children: [
const SizedBox(height: 13),
...pro.servers.map((e) => _buildEachServerCard(e))
],
))),
);
});
return Scaffold(
body: autoUpdate
? child
: SmartRefresher(
controller: _refreshController,
child: child,
onRefresh: () async {
await _serverProvider.refreshData();
_refreshController.refreshCompleted();
},
),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () => onPressed: () =>
AppRoute(const ServerEditPage(), 'Add server info page') AppRoute(const ServerEditPage(), 'Add server info page')
@@ -113,6 +129,12 @@ class _ServerPageState extends State<ServerPage>
final rootDisk = final rootDisk =
ss.disk.firstWhere((element) => element.mountLocation == '/'); ss.disk.firstWhere((element) => element.mountLocation == '/');
final topRightStr =
getTopRightStr(cs, ss.cpu2Status.temp, ss.uptime, ss.failedInfo);
final hasError = cs == ServerConnectionState.failed;
final style = TextStyle(
color: _theme.textTheme.bodyText1!.color!.withAlpha(100), fontSize: 11);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -124,11 +146,18 @@ class _ServerPageState extends State<ServerPage>
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
textScaleFactor: 1.0, textScaleFactor: 1.0,
), ),
Text(getTopRightStr(cs, ss.cpu2Status.temp, ss.uptime, ss.failedInfo), hasError
textScaleFactor: 1.0, ? ConstrainedBox(
style: TextStyle( constraints: BoxConstraints(
color: _theme.textTheme.bodyText1!.color!.withAlpha(100), maxWidth: _media.size.width * 0.57, maxHeight: 17),
fontSize: 11)) child: Marquee(
accelerationDuration: const Duration(seconds: 3),
accelerationCurve: Curves.linear,
decelerationDuration: const Duration(seconds: 3),
decelerationCurve: Curves.linear,
text: topRightStr, textScaleFactor: 1.0, style: style),
)
: Text(topRightStr, style: style, textScaleFactor: 1.0),
], ],
), ),
const SizedBox( const SizedBox(
@@ -149,7 +178,8 @@ class _ServerPageState extends State<ServerPage>
); );
} }
String getTopRightStr(ServerConnectionState cs, String temp, String upTime, String? failedInfo) { String getTopRightStr(ServerConnectionState cs, String temp, String upTime,
String? failedInfo) {
switch (cs) { switch (cs) {
case ServerConnectionState.disconnected: case ServerConnectionState.disconnected:
return 'Disconnected'; return 'Disconnected';

View File

@@ -113,7 +113,11 @@ class _SettingPageState extends State<SettingPage> {
height: 3, height: 3,
), ),
_intervalValue == 0.0 _intervalValue == 0.0
? const Text('You set to 0, will not update automatically.') ? const Text(
'You set to 0, will not update automatically.\nYou can pull to refresh manually.',
style: TextStyle(color: Colors.grey),
textAlign: TextAlign.center,
)
: const SizedBox(), : const SizedBox(),
const SizedBox( const SizedBox(
height: 13, height: 13,

View File

@@ -122,7 +122,8 @@ class _SnippetListPageState extends State<SnippetListPage> {
final result = await locator<ServerProvider>() final result = await locator<ServerProvider>()
.runSnippet(_selectedIndex, snippet); .runSnippet(_selectedIndex, snippet);
if (result != null) { if (result != null) {
showRoundDialog(context, 'Result', Text(result, style: const TextStyle(fontSize: 13)), [ showRoundDialog(context, 'Result',
Text(result, style: const TextStyle(fontSize: 13)), [
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('Close')) child: const Text('Close'))

View File

@@ -28,7 +28,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.1" version: "2.8.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@@ -42,7 +42,7 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.0" version: "1.2.0"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@@ -117,6 +117,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.0"
fading_edge_scrollview:
dependency: transitive
description:
name: fading_edge_scrollview
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -258,13 +265,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
marquee:
dependency: "direct main"
description:
name: marquee
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.10" version: "0.12.11"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -363,6 +377,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.1" version: "6.0.1"
pull_to_refresh:
dependency: "direct main"
description:
name: pull_to_refresh
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
r_upgrade: r_upgrade:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -423,7 +444,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.2" version: "0.4.3"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -493,7 +514,7 @@ packages:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:

View File

@@ -52,6 +52,8 @@ dependencies:
ref: main ref: main
clipboard: ^0.1.3 clipboard: ^0.1.3
r_upgrade: ^0.3.6 r_upgrade: ^0.3.6
pull_to_refresh: ^2.0.0
marquee: ^2.2.0
dev_dependencies: dev_dependencies: