mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-11-10 19:03:12 +00:00
feat(metrics): Implement disk usage metrics
- Refactor metrics_cubit - Implement fallback to legacy when less than 20 dots
This commit is contained in:
parent
48c7d7be2c
commit
68f34dc7b7
|
@ -119,4 +119,60 @@ mixin MonitoringApi on GraphQLApiMap {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<GenericResult<DiskMetrics?>> getDiskMetrics({
|
||||||
|
required final int step,
|
||||||
|
required final DateTime start,
|
||||||
|
required final DateTime end,
|
||||||
|
}) async {
|
||||||
|
QueryResult<Query$GetDiskMetrics> response;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final GraphQLClient client = await getClient();
|
||||||
|
final variables = Variables$Query$GetDiskMetrics(
|
||||||
|
step: step,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
final query = Options$Query$GetDiskMetrics(variables: variables);
|
||||||
|
response = await client.query$GetDiskMetrics(query);
|
||||||
|
if (response.hasException) {
|
||||||
|
print(response.exception.toString());
|
||||||
|
return GenericResult<DiskMetrics?>(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (response.parsedData == null) {
|
||||||
|
return GenericResult<DiskMetrics?>(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (response.parsedData?.monitoring.diskUsage.overallUsage
|
||||||
|
is Fragment$MonitoringQueryError) {
|
||||||
|
return GenericResult<DiskMetrics?>(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final metrics = DiskMetrics.fromGraphQL(
|
||||||
|
data: response.parsedData!.monitoring,
|
||||||
|
stepsInSecond: step,
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
return GenericResult<DiskMetrics?>(
|
||||||
|
success: true,
|
||||||
|
data: metrics,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
return GenericResult<DiskMetrics?>(
|
||||||
|
success: false,
|
||||||
|
data: null,
|
||||||
|
message: e.toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,30 +39,21 @@ class MetricsCubit extends Cubit<MetricsState> {
|
||||||
|
|
||||||
void load(final Period period) async {
|
void load(final Period period) async {
|
||||||
try {
|
try {
|
||||||
final MetricsLoaded newState = await repository.getServerMetrics(period);
|
final MetricsStateUpdate newStateUpdate =
|
||||||
|
await repository.getRelevantServerMetrics(period);
|
||||||
|
|
||||||
|
int duration = newStateUpdate.nextCheckInSeconds;
|
||||||
|
if (duration <= 0) {
|
||||||
|
duration = state.period.stepPeriodInSeconds;
|
||||||
|
}
|
||||||
timer = Timer(
|
timer = Timer(
|
||||||
Duration(seconds: newState.metrics.stepsInSecond.toInt()),
|
Duration(seconds: duration),
|
||||||
() => load(newState.period),
|
() => load(period),
|
||||||
);
|
);
|
||||||
emit(newState);
|
|
||||||
return;
|
emit(newStateUpdate.newState);
|
||||||
} catch (_) {}
|
|
||||||
try {
|
|
||||||
final MetricsLoaded newState = await repository.getLegacyMetrics(period);
|
|
||||||
timer = Timer(
|
|
||||||
Duration(seconds: newState.metrics.stepsInSecond.toInt()),
|
|
||||||
() => load(newState.period),
|
|
||||||
);
|
|
||||||
emit(newState);
|
|
||||||
} on StateError {
|
} on StateError {
|
||||||
print('Tried to emit metrics when cubit is closed');
|
print('Tried to emit metrics when cubit is closed');
|
||||||
} on MetricsLoadException {
|
|
||||||
timer = Timer(
|
|
||||||
Duration(seconds: state.period.stepPeriodInSeconds),
|
|
||||||
() => load(state.period),
|
|
||||||
);
|
|
||||||
} on MetricsUnsupportedException {
|
|
||||||
emit(MetricsUnsupported(period));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,50 @@ class MetricsUnsupportedException implements Exception {
|
||||||
final String message;
|
final String message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MetricsStateUpdate {
|
||||||
|
MetricsStateUpdate(this.newState, this.nextCheckInSeconds);
|
||||||
|
final MetricsState newState;
|
||||||
|
final int nextCheckInSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
class MetricsRepository {
|
class MetricsRepository {
|
||||||
static const String metricsSupportedVersion = '>=3.3.0';
|
static const String metricsSupportedVersion = '>=3.3.0';
|
||||||
Future<MetricsLoaded> getServerMetrics(final Period period) async {
|
|
||||||
|
Future<MetricsStateUpdate> getRelevantServerMetrics(
|
||||||
|
final Period period,
|
||||||
|
) async {
|
||||||
|
MetricsLoaded? state;
|
||||||
|
int nextUpdate = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final stateLoaded = await _getServerMetrics(period);
|
||||||
|
nextUpdate = stateLoaded.metrics.stepsInSecond.toInt();
|
||||||
|
state = stateLoaded;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
const minAmountForRendering = 20;
|
||||||
|
|
||||||
|
if (state != null &&
|
||||||
|
state.metrics.cpu.length >= minAmountForRendering &&
|
||||||
|
state.metrics.bandwidthIn.length >= minAmountForRendering &&
|
||||||
|
state.metrics.bandwidthOut.length >= minAmountForRendering) {
|
||||||
|
return MetricsStateUpdate(state, nextUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final stateLoaded = await _getLegacyMetrics(period);
|
||||||
|
nextUpdate = stateLoaded.metrics.stepsInSecond.toInt();
|
||||||
|
state = stateLoaded;
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
if (state != null) {
|
||||||
|
return MetricsStateUpdate(state, nextUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return MetricsStateUpdate(MetricsUnsupported(period), nextUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<MetricsLoaded> _getServerMetrics(final Period period) async {
|
||||||
final String? apiVersion =
|
final String? apiVersion =
|
||||||
getIt<ApiConnectionRepository>().apiData.apiVersion.data;
|
getIt<ApiConnectionRepository>().apiData.apiVersion.data;
|
||||||
if (apiVersion == null) {
|
if (apiVersion == null) {
|
||||||
|
@ -69,15 +110,23 @@ class MetricsRepository {
|
||||||
step: end.difference(start).inSeconds ~/ 120,
|
step: end.difference(start).inSeconds ~/ 120,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final diskResult =
|
||||||
|
await getIt<ApiConnectionRepository>().api.getDiskMetrics(
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
step: end.difference(start).inSeconds ~/ 120,
|
||||||
|
);
|
||||||
|
|
||||||
return MetricsLoaded(
|
return MetricsLoaded(
|
||||||
period: period,
|
period: period,
|
||||||
metrics: result.data!,
|
metrics: result.data!,
|
||||||
source: MetricsDataSource.server,
|
source: MetricsDataSource.server,
|
||||||
memoryMetrics: memoryResult.data,
|
memoryMetrics: memoryResult.data,
|
||||||
|
diskMetrics: diskResult.data,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<MetricsLoaded> getLegacyMetrics(final Period period) async {
|
Future<MetricsLoaded> _getLegacyMetrics(final Period period) async {
|
||||||
if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) {
|
if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) {
|
||||||
throw MetricsUnsupportedException('Server Provider data is null');
|
throw MetricsUnsupportedException('Server Provider data is null');
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,8 +146,36 @@ class DiskMetrics {
|
||||||
required this.end,
|
required this.end,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
DiskMetrics.fromGraphQL({
|
||||||
|
required final Query$GetDiskMetrics$monitoring data,
|
||||||
|
required final int stepsInSecond,
|
||||||
|
required final DateTime start,
|
||||||
|
required final DateTime end,
|
||||||
|
}) : this(
|
||||||
|
stepsInSecond: stepsInSecond,
|
||||||
|
diskMetrics: Map.fromEntries(
|
||||||
|
(data.diskUsage.overallUsage as Fragment$MonitoringMetrics)
|
||||||
|
.metrics
|
||||||
|
.map(
|
||||||
|
(final metric) => MapEntry(
|
||||||
|
metric.metricId,
|
||||||
|
metric.values
|
||||||
|
.map(
|
||||||
|
(final value) => TimeSeriesData(
|
||||||
|
value.timestamp.millisecondsSinceEpoch ~/ 1000,
|
||||||
|
double.parse(value.value),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
);
|
||||||
|
|
||||||
final num stepsInSecond;
|
final num stepsInSecond;
|
||||||
final List<TimeSeriesData> diskMetrics;
|
final Map<String, List<TimeSeriesData>> diskMetrics;
|
||||||
|
|
||||||
final DateTime start;
|
final DateTime start;
|
||||||
final DateTime end;
|
final DateTime end;
|
||||||
|
|
|
@ -145,6 +145,68 @@ class _Chart extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (state is MetricsLoaded && state.diskMetrics != null)
|
||||||
|
FilledCard(
|
||||||
|
clipped: false,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
'resource_chart.network_title'.tr(),
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
fit: FlexFit.loose,
|
||||||
|
child: Wrap(
|
||||||
|
spacing: 8.0,
|
||||||
|
runSpacing: 8.0,
|
||||||
|
alignment: WrapAlignment.end,
|
||||||
|
runAlignment: WrapAlignment.end,
|
||||||
|
children: [
|
||||||
|
Legend(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
text: 'resource_chart.in'.tr(),
|
||||||
|
),
|
||||||
|
Legend(
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
|
text: 'resource_chart.out'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
getDiskChart(state),
|
||||||
|
AnimatedOpacity(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
opacity: state is MetricsLoading ? 1 : 0,
|
||||||
|
child: const _GraphLoadingCardContent(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
];
|
];
|
||||||
} else if (state is MetricsUnsupported) {
|
} else if (state is MetricsUnsupported) {
|
||||||
charts = [
|
charts = [
|
||||||
|
@ -249,6 +311,23 @@ class _Chart extends StatelessWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget getDiskChart(final MetricsLoaded state) {
|
||||||
|
final data = state.diskMetrics;
|
||||||
|
|
||||||
|
if (data == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: DiskChart(
|
||||||
|
listData: data.diskMetrics.values.toList(),
|
||||||
|
period: state.period,
|
||||||
|
start: state.metrics.start,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
class _GraphLoadingCardContent extends StatelessWidget {
|
class _GraphLoadingCardContent extends StatelessWidget {
|
||||||
const _GraphLoadingCardContent();
|
const _GraphLoadingCardContent();
|
||||||
|
|
||||||
|
|
230
lib/ui/pages/server_details/charts/disk_charts.dart
Normal file
230
lib/ui/pages/server_details/charts/disk_charts.dart
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/disk_size.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
|
||||||
|
|
||||||
|
class DiskChart extends StatelessWidget {
|
||||||
|
const DiskChart({
|
||||||
|
required this.listData,
|
||||||
|
required this.period,
|
||||||
|
required this.start,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final List<List<TimeSeriesData>> listData;
|
||||||
|
final Period period;
|
||||||
|
final DateTime start;
|
||||||
|
|
||||||
|
List<FlSpot> getSpots(final data) {
|
||||||
|
var i = 0;
|
||||||
|
final List<FlSpot> res = [];
|
||||||
|
|
||||||
|
for (final d in data) {
|
||||||
|
res.add(FlSpot(i.toDouble(), d.value));
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(final BuildContext context) => LineChart(
|
||||||
|
LineChartData(
|
||||||
|
lineTouchData: LineTouchData(
|
||||||
|
enabled: true,
|
||||||
|
touchTooltipData: LineTouchTooltipData(
|
||||||
|
getTooltipColor: (final LineBarSpot _) =>
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
tooltipPadding: const EdgeInsets.all(8),
|
||||||
|
getTooltipItems: (final List<LineBarSpot> touchedBarSpots) {
|
||||||
|
final List<LineTooltipItem> res = [];
|
||||||
|
|
||||||
|
bool timeShown = false;
|
||||||
|
|
||||||
|
for (final spot in touchedBarSpots) {
|
||||||
|
final value = spot.y;
|
||||||
|
final date = listData[0][spot.x.toInt()].time;
|
||||||
|
|
||||||
|
res.add(
|
||||||
|
LineTooltipItem(
|
||||||
|
'${timeShown ? '' : DateFormat('HH:mm dd.MM.yyyy').format(date)} ${spot.barIndex == 0 ? 'resource_chart.in'.tr() : 'resource_chart.out'.tr()} ${DiskSize(byte: value.toInt()).toString()}',
|
||||||
|
TextStyle(
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
timeShown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
lineBarsData: [
|
||||||
|
// IN
|
||||||
|
LineChartBarData(
|
||||||
|
spots: getSpots(listData[0]),
|
||||||
|
isCurved: false,
|
||||||
|
barWidth: 2,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
dotData: const FlDotData(
|
||||||
|
show: false,
|
||||||
|
),
|
||||||
|
belowBarData: BarAreaData(
|
||||||
|
show: true,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.primary.withOpacity(0.5),
|
||||||
|
Theme.of(context).colorScheme.primary.withOpacity(0.0),
|
||||||
|
],
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// OUT
|
||||||
|
LineChartBarData(
|
||||||
|
spots: getSpots(listData[1]),
|
||||||
|
isCurved: false,
|
||||||
|
barWidth: 2,
|
||||||
|
color: Theme.of(context).colorScheme.tertiary,
|
||||||
|
dotData: const FlDotData(
|
||||||
|
show: false,
|
||||||
|
),
|
||||||
|
belowBarData: BarAreaData(
|
||||||
|
show: true,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
Theme.of(context).colorScheme.tertiary.withOpacity(0.5),
|
||||||
|
Theme.of(context).colorScheme.tertiary.withOpacity(0.0),
|
||||||
|
],
|
||||||
|
begin: Alignment.bottomCenter,
|
||||||
|
end: Alignment.topCenter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
minY: 0,
|
||||||
|
maxY: [
|
||||||
|
...listData[0].map((final e) => e.value),
|
||||||
|
...listData[1].map((final e) => e.value),
|
||||||
|
].reduce(max) *
|
||||||
|
1.2,
|
||||||
|
minX: 0,
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
topTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
interval: 40,
|
||||||
|
reservedSize: 30,
|
||||||
|
getTitlesWidget: (final value, final titleMeta) => Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
bottomTitle(
|
||||||
|
value.toInt(),
|
||||||
|
listData[0],
|
||||||
|
period,
|
||||||
|
),
|
||||||
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
showTitles: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leftTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
rightTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
reservedSize: 50,
|
||||||
|
getTitlesWidget: (final value, final titleMeta) => Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 5),
|
||||||
|
child: Text(
|
||||||
|
DiskSize(byte: value.toInt()).toString(),
|
||||||
|
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
interval: [
|
||||||
|
...listData[0].map((final e) => e.value),
|
||||||
|
...listData[1].map((final e) => e.value),
|
||||||
|
].reduce(max) *
|
||||||
|
2 /
|
||||||
|
6.5,
|
||||||
|
showTitles: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
gridData: FlGridData(
|
||||||
|
show: true,
|
||||||
|
drawVerticalLine: true,
|
||||||
|
verticalInterval: 40,
|
||||||
|
horizontalInterval: [
|
||||||
|
...listData[0].map((final e) => e.value),
|
||||||
|
...listData[1].map((final e) => e.value),
|
||||||
|
].reduce(max) *
|
||||||
|
2 /
|
||||||
|
6.5,
|
||||||
|
getDrawingHorizontalLine: (final value) => FlLine(
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||||
|
strokeWidth: 1,
|
||||||
|
),
|
||||||
|
getDrawingVerticalLine: (final value) => FlLine(
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||||
|
strokeWidth: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
borderData: FlBorderData(
|
||||||
|
show: true,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
left: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
right: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
top: BorderSide(
|
||||||
|
color: Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
bool checkToShowTitle(
|
||||||
|
final double minValue,
|
||||||
|
final double maxValue,
|
||||||
|
final SideTitles sideTitles,
|
||||||
|
final double appliedInterval,
|
||||||
|
final double value,
|
||||||
|
) {
|
||||||
|
if (value < 0) {
|
||||||
|
return false;
|
||||||
|
} else if (value == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final diff = value - minValue;
|
||||||
|
final finalValue = diff / 20;
|
||||||
|
return finalValue - finalValue.floor() == 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import 'package:selfprivacy/ui/components/cards/filled_card.dart';
|
||||||
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
|
import 'package:selfprivacy/ui/components/list_tiles/list_tile_on_surface_variant.dart';
|
||||||
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/cpu_chart.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/cpu_chart.dart';
|
||||||
|
import 'package:selfprivacy/ui/pages/server_details/charts/disk_charts.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/memory_chart.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/memory_chart.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart';
|
import 'package:selfprivacy/ui/pages/server_details/charts/network_charts.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_storage/storage_card.dart';
|
import 'package:selfprivacy/ui/pages/server_storage/storage_card.dart';
|
||||||
|
|
Loading…
Reference in a new issue