ui to add servers
This commit is contained in:
parent
6d7aa93157
commit
45bdb9023a
9 changed files with 787 additions and 263 deletions
|
@ -15,15 +15,18 @@ class Client {
|
|||
|
||||
open() {
|
||||
channel = IOWebSocketChannel.connect(this.url);
|
||||
int tick = 1;
|
||||
channel.stream.listen((message) {
|
||||
Map data = json.decode(message);
|
||||
Map commands = data['commands'];
|
||||
_metricsController.add(
|
||||
MetricDataPoint(
|
||||
tick,
|
||||
CommandMetrics.from_map(commands['search'] ?? {}),
|
||||
CommandMetrics.from_map(commands['resolve'] ?? {})
|
||||
)
|
||||
);
|
||||
tick++;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -63,11 +66,12 @@ class CommandMetrics {
|
|||
|
||||
|
||||
class MetricDataPoint {
|
||||
final DateTime time = DateTime.now();
|
||||
final int tick;
|
||||
final CommandMetrics search;
|
||||
final CommandMetrics resolve;
|
||||
MetricDataPoint(this.search, this.resolve);
|
||||
MetricDataPoint(this.tick, this.search, this.resolve);
|
||||
MetricDataPoint.empty():
|
||||
tick = 0,
|
||||
search=CommandMetrics.from_map({}),
|
||||
resolve=CommandMetrics.from_map({});
|
||||
}
|
|
@ -1,49 +1,82 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show debugDefaultTargetPlatformOverride;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:ver/src/servers.dart';
|
||||
import 'package:ver/src/models/server.dart';
|
||||
import 'package:ver/utils.dart';
|
||||
import 'package:ver/time_series_chart.dart';
|
||||
|
||||
|
||||
class VerApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Ver',
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light,
|
||||
primarySwatch: Colors.lightBlue,
|
||||
fontFamily: 'Roboto',
|
||||
),
|
||||
home: VerHomePage(title: 'Wallet Server'),
|
||||
);
|
||||
}
|
||||
class UnderConstructionPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Under Construction')),
|
||||
body: SizedBox.expand(
|
||||
child: Center(child: Text('Under Construction')),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class VerHomePage extends StatefulWidget {
|
||||
VerHomePage({Key key, this.title}) : super(key: key);
|
||||
final String title;
|
||||
@override
|
||||
_VerHomePageState createState() => _VerHomePageState();
|
||||
class MainPage extends StatefulWidget {
|
||||
@override
|
||||
_MainPageState createState() => _MainPageState();
|
||||
}
|
||||
|
||||
class _VerHomePageState extends State<VerHomePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: new Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SimpleTimeSeriesChart()
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MainPageState extends State<MainPage> {
|
||||
int _currentIndex = 3;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: IndexedStack(
|
||||
index: _currentIndex,
|
||||
children: [
|
||||
UnderConstructionPage(),
|
||||
UnderConstructionPage(),
|
||||
UnderConstructionPage(),
|
||||
ServersSectionNavigation(),
|
||||
UnderConstructionPage(),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(icon: Icon(Icons.home), title: Text('Home')),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.trending_up), title: Text('Trending')),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.subscriptions), title: Text('Subscriptions')),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.router), title: Text('Servers')),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.folder), title: Text('Library')),
|
||||
],
|
||||
currentIndex: _currentIndex,
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
_currentIndex = index;
|
||||
});
|
||||
},
|
||||
selectedItemColor: Colors.amber[800],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
debugDefaultTargetPlatformOverride = getTargetPlatformForDesktop();
|
||||
runApp(new VerApp());
|
||||
debugDefaultTargetPlatformOverride = getTargetPlatformForDesktop();
|
||||
runApp(
|
||||
MaterialApp(
|
||||
title: 'Ver',
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
//primarySwatch: Colors.lightBlue,
|
||||
fontFamily: 'Roboto',
|
||||
),
|
||||
home: MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<ServerManager>(builder: (context) => ServerManager())
|
||||
],
|
||||
child: MainPage()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
190
dart/packages/ver/lib/src/models/server.dart
Normal file
190
dart/packages/ver/lib/src/models/server.dart
Normal file
|
@ -0,0 +1,190 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:lbry/lbry.dart';
|
||||
|
||||
|
||||
class ServerManager extends ChangeNotifier {
|
||||
final List<Server> _servers = [];
|
||||
UnmodifiableListView<Server> get items => UnmodifiableListView(_servers);
|
||||
|
||||
add(Server server) {
|
||||
_servers.add(server);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
remove(Server server) {
|
||||
server.dispose();
|
||||
_servers.remove(server);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
for (var server in _servers) {
|
||||
server.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Server extends ChangeNotifier {
|
||||
|
||||
String _label = "";
|
||||
String get label => _label;
|
||||
String get labelOrHost => _label.length > 0 ? _label : host;
|
||||
|
||||
String _host = "";
|
||||
String get host => _host;
|
||||
int _port = 8181;
|
||||
int get port => _port;
|
||||
bool _ssl = false;
|
||||
bool get ssl => _ssl;
|
||||
String get url => _connection.url;
|
||||
_setURL(String host, int port, bool ssl) {
|
||||
_host = host;
|
||||
_port = port;
|
||||
_connection.url = "ws${ssl?'s':''}://$host:$port";
|
||||
}
|
||||
|
||||
DateTime _added = new DateTime.now();
|
||||
String get added => _added.toIso8601String();
|
||||
|
||||
bool _isDefault = false;
|
||||
bool get isDefault => _isDefault;
|
||||
|
||||
bool _isEnabled = false;
|
||||
bool get isEnabled => _isEnabled;
|
||||
|
||||
bool _isTrackingServerLoad = false;
|
||||
bool get isTrackingServerLoad => _isTrackingServerLoad;
|
||||
_setIsTrackingServerLoad(bool toggle_tracking) {
|
||||
if (_isTrackingServerLoad && !toggle_tracking) {
|
||||
_connection.unsubscribe_from_server_load_data();
|
||||
} else if (!_isTrackingServerLoad && toggle_tracking) {
|
||||
_connection.subscribe_to_server_load_data();
|
||||
}
|
||||
_isTrackingServerLoad = toggle_tracking;
|
||||
}
|
||||
|
||||
ClientLoadManager clientLoadManager;
|
||||
|
||||
final ServerConnection _connection = ServerConnection();
|
||||
bool get isConnected => _connection.isConnected;
|
||||
Stream<ServerLoadDataPoint> get serverLoadStream => _connection.load_data;
|
||||
final List<ServerLoadDataPoint> serverLoadData = [ServerLoadDataPoint.empty()];
|
||||
|
||||
Server() {
|
||||
clientLoadManager = ClientLoadManager(this);
|
||||
serverLoadStream.listen(serverLoadData.add);
|
||||
}
|
||||
|
||||
update({String host, int port, bool ssl, String label,
|
||||
bool isDefault, bool isEnabled, bool isTrackingServerLoad}) {
|
||||
if (host != null && port != null && ssl != null) {
|
||||
_setURL(host, port, ssl);
|
||||
}
|
||||
if (isTrackingServerLoad != null) {
|
||||
_setIsTrackingServerLoad(isTrackingServerLoad);
|
||||
}
|
||||
_label = label ?? _label;
|
||||
_isDefault = isDefault ?? _isDefault;
|
||||
_isEnabled = isEnabled ?? _isEnabled;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
connect() async {
|
||||
await _connection.open();
|
||||
if (_isTrackingServerLoad) {
|
||||
_connection.subscribe_to_server_load_data();
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
clientLoadManager.stop();
|
||||
_connection.close();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
disconnect();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ClientLoadManager extends ChangeNotifier {
|
||||
final Server _server;
|
||||
|
||||
ClientLoadGenerator clientLoadGenerator;
|
||||
final StreamController<ClientLoadDataPoint> _loadDataController = StreamController.broadcast();
|
||||
Stream<ClientLoadDataPoint> get clientLoadStream => _loadDataController.stream;
|
||||
final List<ClientLoadDataPoint> clientLoadData = [ClientLoadDataPoint.empty()];
|
||||
|
||||
int _load = 1;
|
||||
int get load => _load;
|
||||
int _offset = 0;
|
||||
int get offset => _offset;
|
||||
bool _noTotals = false;
|
||||
bool get noTotals => _noTotals;
|
||||
|
||||
ClientLoadManager(this._server) {
|
||||
clientLoadStream.listen(clientLoadData.add);
|
||||
}
|
||||
|
||||
update({int load, int offset, bool noTotals}) {
|
||||
_load = load ?? _load;
|
||||
_offset = offset ?? _offset;
|
||||
_noTotals = noTotals ?? _noTotals;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
start() {
|
||||
clientLoadData.clear();
|
||||
clientLoadData.add(ClientLoadDataPoint.empty());
|
||||
clientLoadGenerator = ClientLoadGenerator(
|
||||
_server.host, _server.port,
|
||||
query: {
|
||||
'id': 1,
|
||||
'method': 'blockchain.claimtrie.search',
|
||||
'params': {
|
||||
'no_totals': _noTotals,
|
||||
'offset': _offset,
|
||||
'limit': 20,
|
||||
'fee_amount': '<1',
|
||||
//'all_tags': ['funny'],
|
||||
'any_tags': [
|
||||
'crypto',
|
||||
'outdoors',
|
||||
'cars',
|
||||
'automotive'
|
||||
]
|
||||
}
|
||||
}, tickCallback: (t, stats) {
|
||||
_loadDataController.add(stats);
|
||||
//increase = max(1, min(100, increase+2)-stats.backlog);
|
||||
//increase += 1;
|
||||
//t.query['params']['offset'] = (increase/2).ceil()*t.query['params']['limit'];
|
||||
t.load = _load;//rand.nextInt(10)+5;
|
||||
return true;
|
||||
})..start();
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (clientLoadGenerator != null) {
|
||||
clientLoadGenerator.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
stop();
|
||||
}
|
||||
|
||||
}
|
73
dart/packages/ver/lib/src/servers.dart
Normal file
73
dart/packages/ver/lib/src/servers.dart
Normal file
|
@ -0,0 +1,73 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:ver/src/widgets/server.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'models/server.dart';
|
||||
|
||||
|
||||
class ServerListPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
Scaffold(
|
||||
appBar: AppBar(title: Text('Servers')),
|
||||
body: ServerList(),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: Icon(Icons.add),
|
||||
onPressed: () => Navigator.of(context).pushNamed('/edit', arguments: true),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class ServerFormPage extends StatelessWidget {
|
||||
|
||||
final bool creating;
|
||||
ServerFormPage(this.creating);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
Scaffold(
|
||||
appBar: AppBar(title: Text(creating ? 'Add Server' : 'Modify Server')),
|
||||
body: ServerForm(creating),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class ServerViewPage extends StatelessWidget {
|
||||
final Server server;
|
||||
ServerViewPage(this.server);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
Scaffold(
|
||||
appBar: AppBar(title: Text(server.labelOrHost)),
|
||||
body: ChangeNotifierProvider<Server>.value(
|
||||
value: server,
|
||||
child: ServerView()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class ServersSectionNavigation extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return MaterialPageRoute(
|
||||
settings: settings,
|
||||
builder: (BuildContext context) {
|
||||
switch (settings.name) {
|
||||
case '/':
|
||||
return ServerListPage();
|
||||
case '/edit':
|
||||
return ServerFormPage(settings.arguments);
|
||||
case '/view':
|
||||
return ServerViewPage(settings.arguments);
|
||||
}
|
||||
return ServerListPage();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
172
dart/packages/ver/lib/src/widgets/server.dart
Normal file
172
dart/packages/ver/lib/src/widgets/server.dart
Normal file
|
@ -0,0 +1,172 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../models/server.dart';
|
||||
import 'time_series_chart.dart';
|
||||
|
||||
|
||||
class ServerList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<ServerManager>(
|
||||
builder: (_, servers, __) =>
|
||||
ListView.builder(
|
||||
itemCount: servers.items.length,
|
||||
itemBuilder: (context, index) =>
|
||||
ChangeNotifierProvider<Server>.value(
|
||||
value: servers.items[index],
|
||||
child: ServerListItem()
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class ServerListItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Consumer<Server>(
|
||||
builder: (_, server, __) =>
|
||||
ListTile(
|
||||
leading: FlutterLogo(size: 72.0),
|
||||
title: Text(server.labelOrHost),
|
||||
subtitle: Text("${server.url}\nadded ${server.added}"),
|
||||
trailing: Icon(Icons.more_vert),
|
||||
isThreeLine: true,
|
||||
onTap: ()=> Navigator.of(context).pushNamed('/view', arguments: server),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ServerView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ServerCharts();
|
||||
}
|
||||
|
||||
|
||||
class ServerForm extends StatefulWidget {
|
||||
final bool creating;
|
||||
|
||||
ServerForm(this.creating);
|
||||
|
||||
@override
|
||||
_ServerFormState createState() => _ServerFormState();
|
||||
}
|
||||
|
||||
|
||||
class _ServerFormState extends State<ServerForm> {
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _formData = {
|
||||
'label': '',
|
||||
'host': 'localhost',
|
||||
'port': 8181,
|
||||
'ssl': false,
|
||||
'isDefault': true,
|
||||
'isEnabled': false,
|
||||
'isTrackingServerLoad': false
|
||||
};
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
ListTile(title: TextFormField(
|
||||
initialValue: _formData['label'],
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Label',
|
||||
hintText: 'Optional text to display in server list.'
|
||||
),
|
||||
onSaved: (value) => _formData['label'] = value,
|
||||
)),
|
||||
ListTile(title: TextFormField(
|
||||
initialValue: _formData['host'],
|
||||
keyboardType: TextInputType.url,
|
||||
inputFormatters: [
|
||||
WhitelistingTextInputFormatter(RegExp(r'[\w\-\.]+'))
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Host Name',
|
||||
hintText: 'Enter the host name of the server.'
|
||||
),
|
||||
validator: (value) => value.isEmpty ? 'A host name is required.' : null,
|
||||
onSaved: (value) => _formData['host'] = value,
|
||||
)),
|
||||
ListTile(title: TextFormField(
|
||||
initialValue: _formData['port'].toString(),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
WhitelistingTextInputFormatter.digitsOnly
|
||||
],
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Port',
|
||||
hintText: 'Enter the port of the server.'
|
||||
),
|
||||
validator: (value) => value.isEmpty ? 'A port is required.' : null,
|
||||
onSaved: (value) => setState(() => _formData['port'] = int.parse(value)),
|
||||
)),
|
||||
SwitchListTile(
|
||||
title: Text('Requires SSL.'),
|
||||
value: _formData['ssl'],
|
||||
onChanged: (value) => setState(() => _formData['ssl'] = value),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text('Use this as your primary and default server.'),
|
||||
value: _formData['isDefault'],
|
||||
onChanged: (value) => setState(() => _formData['isDefault'] = value),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text('Always stay connected.'),
|
||||
value: _formData['isEnabled'],
|
||||
onChanged: (value) => setState(() => _formData['isEnabled'] = value),
|
||||
),
|
||||
SwitchListTile(
|
||||
title: Text('Track server load.'),
|
||||
value: _formData['isTrackingServerLoad'],
|
||||
onChanged: (value) =>
|
||||
setState(() => _formData['isTrackingServerLoad'] = value),
|
||||
),
|
||||
ListTile(title: RaisedButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState.validate()) {
|
||||
_formKey.currentState.save();
|
||||
var manager = Provider.of<ServerManager>(
|
||||
context, listen: false
|
||||
);
|
||||
var server = Server();
|
||||
server.update(
|
||||
label: _formData['label'],
|
||||
host: _formData['host'],
|
||||
port: _formData['port'],
|
||||
ssl: _formData['ssl'],
|
||||
isDefault: _formData['isDefault'],
|
||||
isEnabled: _formData['isEnabled'],
|
||||
isTrackingServerLoad: _formData['isTrackingServerLoad'],
|
||||
);
|
||||
manager.add(server);
|
||||
if (server.isEnabled) {
|
||||
server.connect();
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Text(widget.creating ? 'Add Server' : 'Update Server'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
270
dart/packages/ver/lib/src/widgets/time_series_chart.dart
Normal file
270
dart/packages/ver/lib/src/widgets/time_series_chart.dart
Normal file
|
@ -0,0 +1,270 @@
|
|||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:charts_flutter/src/base_chart_state.dart' as state;
|
||||
import 'package:charts_common/common.dart' as common;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:lbry/lbry.dart';
|
||||
import '../models/server.dart';
|
||||
|
||||
|
||||
class ServerCharts extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var server = Provider.of<Server>(context, listen: false);
|
||||
return ListView(children: <Widget>[
|
||||
SizedBox(height: 220.0, child: ServerLoadChart(server)),
|
||||
SizedBox(height: 220.0, child: ServerPerformanceChart(server)),
|
||||
SizedBox(height: 220.0, child: ClientLoadChart(server.clientLoadManager)),
|
||||
SizedBox(height: 220.0, child: ClientPerformanceChart(server.clientLoadManager)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ServerLoadChart extends StatefulWidget {
|
||||
final Server server;
|
||||
ServerLoadChart(this.server);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => ServerLoadChartState();
|
||||
}
|
||||
|
||||
|
||||
class ServerLoadChartState extends State<ServerLoadChart> {
|
||||
|
||||
List<charts.Series<ServerLoadDataPoint, int>> seriesData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
seriesData = [
|
||||
charts.Series<ServerLoadDataPoint, int>(
|
||||
id: 'Searches Started',
|
||||
colorFn: (_, __) =>
|
||||
charts.MaterialPalette.deepOrange.shadeDefault.lighter,
|
||||
domainFn: (ServerLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ServerLoadDataPoint load, _) => load.search.started,
|
||||
data: widget.server.serverLoadData,
|
||||
),
|
||||
charts.Series<ServerLoadDataPoint, int>(
|
||||
id: 'Searches Finished',
|
||||
colorFn: (_, __) =>
|
||||
charts.MaterialPalette.deepOrange.shadeDefault.darker,
|
||||
domainFn: (ServerLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ServerLoadDataPoint load, _) => load.search.finished,
|
||||
data: widget.server.serverLoadData,
|
||||
),
|
||||
charts.Series<ServerLoadDataPoint, int>(
|
||||
id: 'Resolves Started',
|
||||
colorFn: (_, __) =>
|
||||
charts.MaterialPalette.teal.shadeDefault.lighter,
|
||||
domainFn: (ServerLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ServerLoadDataPoint load, _) => load.resolve.started,
|
||||
data: widget.server.serverLoadData,
|
||||
),
|
||||
charts.Series<ServerLoadDataPoint, int>(
|
||||
id: 'Resolves Finished',
|
||||
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.darker,
|
||||
domainFn: (ServerLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ServerLoadDataPoint load, _) => load.resolve.finished,
|
||||
data: widget.server.serverLoadData,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<ServerLoadDataPoint>(
|
||||
stream: widget.server.serverLoadStream,
|
||||
builder: (BuildContext context, _) => BetterLineChart(seriesData)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ServerPerformanceChart extends StatefulWidget {
|
||||
final Server server;
|
||||
ServerPerformanceChart(this.server);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => ServerPerformanceChartState();
|
||||
}
|
||||
|
||||
|
||||
class ServerPerformanceChartState extends State<ServerPerformanceChart> {
|
||||
|
||||
List<charts.Series<ServerLoadDataPoint, int>> seriesData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
seriesData = [
|
||||
charts.Series<ServerLoadDataPoint, int>(
|
||||
id: 'Avg. Waiting',
|
||||
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault.darker,
|
||||
domainFn: (ServerLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ServerLoadDataPoint load, _) => load.search.avg_wait_time,
|
||||
data: widget.server.serverLoadData,
|
||||
),
|
||||
charts.Series<ServerLoadDataPoint, int>(
|
||||
id: 'Avg. Executing',
|
||||
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.lighter,
|
||||
domainFn: (ServerLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ServerLoadDataPoint load, _) => load.search.avg_execution_time,
|
||||
data: widget.server.serverLoadData,
|
||||
),
|
||||
charts.Series<ServerLoadDataPoint, int>(
|
||||
id: 'Avg. SQLite',
|
||||
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault.darker,
|
||||
domainFn: (ServerLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ServerLoadDataPoint load, _) => load.search.avg_query_time_per_search,
|
||||
data: widget.server.serverLoadData,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<ServerLoadDataPoint>(
|
||||
stream: widget.server.serverLoadStream,
|
||||
builder: (BuildContext context, _) => BetterLineChart(seriesData)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
class ClientLoadChart extends StatefulWidget {
|
||||
final ClientLoadManager client;
|
||||
ClientLoadChart(this.client);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => ClientLoadChartState();
|
||||
}
|
||||
|
||||
|
||||
class ClientLoadChartState extends State<ClientLoadChart> {
|
||||
|
||||
List<charts.Series<ClientLoadDataPoint, int>> seriesData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
seriesData = [
|
||||
charts.Series<ClientLoadDataPoint, int>(
|
||||
id: 'Load',
|
||||
colorFn: (_, __) => charts.MaterialPalette.black.darker,
|
||||
domainFn: (ClientLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ClientLoadDataPoint load, _) => load.load,
|
||||
data: widget.client.clientLoadData,
|
||||
),
|
||||
charts.Series<ClientLoadDataPoint, int>(
|
||||
id: 'Success',
|
||||
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
|
||||
domainFn: (ClientLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ClientLoadDataPoint load, _) => load.success,
|
||||
data: widget.client.clientLoadData,
|
||||
),
|
||||
charts.Series<ClientLoadDataPoint, int>(
|
||||
id: 'Backlog',
|
||||
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
|
||||
domainFn: (ClientLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ClientLoadDataPoint load, _) => load.backlog,
|
||||
data: widget.client.clientLoadData,
|
||||
),
|
||||
charts.Series<ClientLoadDataPoint, int>(
|
||||
id: 'Catch-up',
|
||||
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
|
||||
domainFn: (ClientLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ClientLoadDataPoint load, _) => load.catchup,
|
||||
data: widget.client.clientLoadData,
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<ClientLoadDataPoint>(
|
||||
stream: widget.client.clientLoadStream,
|
||||
builder: (BuildContext context, _) => BetterLineChart(seriesData)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ClientPerformanceChart extends StatefulWidget {
|
||||
final ClientLoadManager client;
|
||||
ClientPerformanceChart(this.client);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => ClientPerformanceChartState();
|
||||
}
|
||||
|
||||
|
||||
class ClientPerformanceChartState extends State<ClientPerformanceChart> {
|
||||
|
||||
List<charts.Series<ClientLoadDataPoint, int>> seriesData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
seriesData = [
|
||||
charts.Series<ClientLoadDataPoint, int>(
|
||||
id: 'Avg. Success Time',
|
||||
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
|
||||
domainFn: (ClientLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ClientLoadDataPoint load, _) => load.avg_success,
|
||||
data: widget.client.clientLoadData,
|
||||
),
|
||||
charts.Series<ClientLoadDataPoint, int>(
|
||||
id: 'Avg. Catch-up Time',
|
||||
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
|
||||
domainFn: (ClientLoadDataPoint load, _) => load.tick,
|
||||
measureFn: (ClientLoadDataPoint load, _) => load.avg_catchup,
|
||||
data: widget.client.clientLoadData,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return StreamBuilder<ClientLoadDataPoint>(
|
||||
stream: widget.client.clientLoadStream,
|
||||
builder: (BuildContext context, _) => BetterLineChart(seriesData)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BetterLineChart extends charts.LineChart {
|
||||
|
||||
final int itemCount;
|
||||
final Object lastItem;
|
||||
|
||||
BetterLineChart(List<charts.Series<dynamic, int>> seriesList)
|
||||
:
|
||||
itemCount = seriesList[0].data.length,
|
||||
lastItem = seriesList[0].data.last,
|
||||
super(
|
||||
seriesList,
|
||||
behaviors: [charts.SeriesLegend()],
|
||||
domainAxis: charts.NumericAxisSpec(
|
||||
viewport: new charts.NumericExtents(
|
||||
max(0, seriesList[0].data.last.tick - 60), seriesList[0].data.last.tick
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@override
|
||||
void updateCommonChart(common.BaseChart baseChart, charts.BaseChart oldWidget,
|
||||
state.BaseChartState chartState) {
|
||||
super.updateCommonChart(baseChart, oldWidget, chartState);
|
||||
final prev = oldWidget as BetterLineChart;
|
||||
if (itemCount != prev?.itemCount || lastItem != prev?.lastItem) {
|
||||
chartState.markChartDirty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,226 +0,0 @@
|
|||
import 'dart:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:charts_flutter/src/base_chart_state.dart' as state;
|
||||
import 'package:charts_common/common.dart' as common;
|
||||
import 'package:lbry/lbry.dart';
|
||||
|
||||
|
||||
class SimpleTimeSeriesChart extends StatefulWidget {
|
||||
SimpleTimeSeriesChart({Key key}) : super(key: key);
|
||||
@override
|
||||
_SimpleTimeSeriesChartState createState() => _SimpleTimeSeriesChartState();
|
||||
}
|
||||
|
||||
|
||||
class _SimpleTimeSeriesChartState extends State<SimpleTimeSeriesChart> {
|
||||
final List<MetricDataPoint> metricData = [];
|
||||
final List<LoadDataPoint> loadData = [];
|
||||
final List<charts.Series<LoadDataPoint, DateTime>> loadSeries = [];
|
||||
final List<charts.Series<LoadDataPoint, DateTime>> timeSeries = [];
|
||||
final List<charts.Series<MetricDataPoint, DateTime>> metricSeries = [];
|
||||
final List<charts.Series<MetricDataPoint, DateTime>> metricTimeSeries = [];
|
||||
final Random rand = Random();
|
||||
LoadGenerator loadGenerator;
|
||||
Client client;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
metricSeries.add(
|
||||
charts.Series<MetricDataPoint, DateTime>(
|
||||
id: 'Searches Started',
|
||||
colorFn: (_, __) => charts.MaterialPalette.deepOrange.shadeDefault.lighter,
|
||||
domainFn: (MetricDataPoint load, _) => load.time,
|
||||
measureFn: (MetricDataPoint load, _) => load.search.started,
|
||||
data: metricData,
|
||||
)
|
||||
);
|
||||
metricSeries.add(
|
||||
charts.Series<MetricDataPoint, DateTime>(
|
||||
id: 'Searches Finished',
|
||||
colorFn: (_, __) => charts.MaterialPalette.deepOrange.shadeDefault.darker,
|
||||
domainFn: (MetricDataPoint load, _) => load.time,
|
||||
measureFn: (MetricDataPoint load, _) => load.search.finished,
|
||||
data: metricData,
|
||||
)
|
||||
);
|
||||
metricSeries.add(
|
||||
charts.Series<MetricDataPoint, DateTime>(
|
||||
id: 'Resolves Started',
|
||||
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.lighter,
|
||||
domainFn: (MetricDataPoint load, _) => load.time,
|
||||
measureFn: (MetricDataPoint load, _) => load.resolve.started,
|
||||
data: metricData,
|
||||
)
|
||||
);
|
||||
metricSeries.add(
|
||||
charts.Series<MetricDataPoint, DateTime>(
|
||||
id: 'Resolves Finished',
|
||||
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.darker,
|
||||
domainFn: (MetricDataPoint load, _) => load.time,
|
||||
measureFn: (MetricDataPoint load, _) => load.resolve.finished,
|
||||
data: metricData,
|
||||
)
|
||||
);
|
||||
metricTimeSeries.add(
|
||||
charts.Series<MetricDataPoint, DateTime>(
|
||||
id: 'Avg. Waiting',
|
||||
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault.darker,
|
||||
domainFn: (MetricDataPoint load, _) => load.time,
|
||||
measureFn: (MetricDataPoint load, _) => load.search.avg_wait_time,
|
||||
data: metricData,
|
||||
)
|
||||
);
|
||||
metricTimeSeries.add(
|
||||
charts.Series<MetricDataPoint, DateTime>(
|
||||
id: 'Avg. Executing',
|
||||
colorFn: (_, __) => charts.MaterialPalette.teal.shadeDefault.lighter,
|
||||
domainFn: (MetricDataPoint load, _) => load.time,
|
||||
measureFn: (MetricDataPoint load, _) => load.search.avg_execution_time,
|
||||
data: metricData,
|
||||
)
|
||||
);
|
||||
metricTimeSeries.add(
|
||||
charts.Series<MetricDataPoint, DateTime>(
|
||||
id: 'Avg. SQLite',
|
||||
colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault.darker,
|
||||
domainFn: (MetricDataPoint load, _) => load.time,
|
||||
measureFn: (MetricDataPoint load, _) => load.search.avg_query_time_per_search,
|
||||
data: metricData,
|
||||
)
|
||||
);
|
||||
loadSeries.add(
|
||||
charts.Series<LoadDataPoint, DateTime>(
|
||||
id: 'Load',
|
||||
colorFn: (_, __) => charts.MaterialPalette.black.darker,
|
||||
domainFn: (LoadDataPoint load, _) => load.time,
|
||||
measureFn: (LoadDataPoint load, _) => load.load,
|
||||
data: loadData,
|
||||
)
|
||||
);
|
||||
loadSeries.add(
|
||||
charts.Series<LoadDataPoint, DateTime>(
|
||||
id: 'Success',
|
||||
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
|
||||
domainFn: (LoadDataPoint load, _) => load.time,
|
||||
measureFn: (LoadDataPoint load, _) => load.success,
|
||||
data: loadData,
|
||||
)
|
||||
);
|
||||
loadSeries.add(
|
||||
charts.Series<LoadDataPoint, DateTime>(
|
||||
id: 'Backlog',
|
||||
colorFn: (_, __) => charts.MaterialPalette.red.shadeDefault,
|
||||
domainFn: (LoadDataPoint load, _) => load.time,
|
||||
measureFn: (LoadDataPoint load, _) => load.backlog,
|
||||
data: loadData,
|
||||
)
|
||||
);
|
||||
loadSeries.add(
|
||||
charts.Series<LoadDataPoint, DateTime>(
|
||||
id: 'Catch-up',
|
||||
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
|
||||
domainFn: (LoadDataPoint load, _) => load.time,
|
||||
measureFn: (LoadDataPoint load, _) => load.catchup,
|
||||
data: loadData,
|
||||
)
|
||||
);
|
||||
timeSeries.add(
|
||||
charts.Series<LoadDataPoint, DateTime>(
|
||||
id: 'Avg. Success Time',
|
||||
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
|
||||
domainFn: (LoadDataPoint load, _) => load.time,
|
||||
measureFn: (LoadDataPoint load, _) => load.avg_success,
|
||||
data: loadData,
|
||||
)
|
||||
);
|
||||
timeSeries.add(
|
||||
charts.Series<LoadDataPoint, DateTime>(
|
||||
id: 'Avg. Catch-up Time',
|
||||
colorFn: (_, __) => charts.MaterialPalette.yellow.shadeDefault,
|
||||
domainFn: (LoadDataPoint load, _) => load.time,
|
||||
measureFn: (LoadDataPoint load, _) => load.avg_catchup,
|
||||
data: loadData,
|
||||
)
|
||||
);
|
||||
var increase = 1;
|
||||
loadData.add(LoadDataPoint());
|
||||
loadGenerator = LoadGenerator('localhost', 50001, {
|
||||
'id': 1,
|
||||
'method': 'blockchain.claimtrie.search',
|
||||
'params': {
|
||||
'no_totals': true,
|
||||
'offset': 0,
|
||||
'limit': 20,
|
||||
'fee_amount': '<1',
|
||||
//'all_tags': ['funny'],
|
||||
'any_tags': [
|
||||
'crypto',
|
||||
'outdoors',
|
||||
'cars',
|
||||
'automotive'
|
||||
]
|
||||
}
|
||||
}, (t, stats) {
|
||||
setState(() {
|
||||
//if (loadData.length > 60) loadData.removeAt(0);
|
||||
loadData.add(stats);
|
||||
});
|
||||
increase = max(1, min(100, increase+2)-stats.backlog);
|
||||
//increase += 1;
|
||||
//t.query['params']['offset'] = (increase/2).ceil()*t.query['params']['limit'];
|
||||
t.load = increase;//rand.nextInt(10)+5;
|
||||
return true;
|
||||
})..start();
|
||||
metricData.add(MetricDataPoint.empty());
|
||||
client = Client('ws://localhost:8181/')..open()..metrics.listen((m) {
|
||||
setState(() {
|
||||
metricData.add(m);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (client != null) client.close();
|
||||
if (loadGenerator != null) loadGenerator.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: <Widget>[
|
||||
SizedBox(height: 220.0, child: BetterTimeSeriesChart(loadSeries)),
|
||||
SizedBox(height: 220.0, child: BetterTimeSeriesChart(timeSeries)),
|
||||
SizedBox(height: 220.0, child: BetterTimeSeriesChart(metricSeries)),
|
||||
SizedBox(height: 220.0, child: BetterTimeSeriesChart(metricTimeSeries)),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class BetterTimeSeriesChart extends charts.TimeSeriesChart {
|
||||
|
||||
final int itemCount;
|
||||
final Object lastItem;
|
||||
|
||||
BetterTimeSeriesChart(
|
||||
List<charts.Series<dynamic, DateTime>> seriesList):
|
||||
itemCount = seriesList[0].data.length,
|
||||
lastItem = seriesList[0].data.last,
|
||||
super(seriesList, behaviors: [charts.SeriesLegend()]);
|
||||
|
||||
@override
|
||||
void updateCommonChart(common.BaseChart baseChart, charts.BaseChart oldWidget,
|
||||
state.BaseChartState chartState) {
|
||||
super.updateCommonChart(baseChart, oldWidget, chartState);
|
||||
final prev = oldWidget as BetterTimeSeriesChart;
|
||||
if (itemCount != prev?.itemCount || lastItem != prev?.lastItem) {
|
||||
chartState.markChartDirty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -151,6 +151,13 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.13.15"
|
||||
provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.0+1"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -12,6 +12,7 @@ dependencies:
|
|||
sdk: flutter
|
||||
cupertino_icons: ^0.1.2
|
||||
charts_flutter: ^0.6.0
|
||||
provider: ^3.0.0
|
||||
lbry:
|
||||
path: ../lbry
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue