feat: CPU, Network and RAM stats from the server

This commit is contained in:
Inex Code 2024-07-30 07:13:56 +03:00
parent 8fe0de0c9e
commit e065463ffb
16 changed files with 9747 additions and 6 deletions

View file

@ -139,7 +139,12 @@
"network_title": "Network Usage",
"in": "In",
"out": "Out",
"unsupported": "You can't view resource usage charts without the server provider token."
"memory": "Memory usage",
"view_usage_by_service": "View usage by service",
"unsupported": "You can't view resource usage charts without the server provider token.",
"system": "System",
"ssh_users": "SSH users",
"ram_usage": "Average usage: {average}. Maximum: {max}."
},
"server": {
"card_title": "Server",

View file

@ -0,0 +1,68 @@
fragment MonitoringMetrics on MonitoringMetrics {
metrics {
metricId
values {
timestamp
value
}
}
}
fragment MonitoringValues on MonitoringValues {
values {
value
timestamp
}
}
fragment MonitoringQueryError on MonitoringQueryError {
error
}
query GetOverallCpuAndNetworkMetrics($start: DateTime, $end: DateTime, $step: Int!) {
monitoring {
cpuUsage(start: $start, end: $end, step: $step) {
overallUsage {
... MonitoringQueryError
... MonitoringValues
}
}
networkUsage(start: $start, end: $end, step: $step) {
overallUsage {
... MonitoringQueryError
... MonitoringMetrics
}
}
}
}
query GetMemoryMetrics($start: DateTime, $end: DateTime, $step: Int!) {
monitoring {
memoryUsage(start: $start, end: $end, step: $step) {
overallUsage {
... MonitoringQueryError
... MonitoringValues
}
averageUsageByService {
... MonitoringQueryError
... MonitoringMetrics
}
maxUsageByService {
... MonitoringQueryError
... MonitoringMetrics
}
}
}
}
query GetDiskMetrics($start: DateTime, $end: DateTime, $step: Int!) {
monitoring {
diskUsage(start: $start, end: $end, step: $step) {
overallUsage {
... MonitoringQueryError
... MonitoringMetrics
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -280,7 +280,7 @@ type Monitoring {
}
type MonitoringMetric {
id: String!
metricId: String!
values: [MonitoringValue!]!
}

View file

@ -0,0 +1,122 @@
part of 'server_api.dart';
mixin MonitoringApi on GraphQLApiMap {
Future<GenericResult<ServerMetrics?>> getServerMetrics({
required final int step,
required final DateTime start,
required final DateTime end,
}) async {
QueryResult<Query$GetOverallCpuAndNetworkMetrics> response;
try {
final GraphQLClient client = await getClient();
final variables = Variables$Query$GetOverallCpuAndNetworkMetrics(
step: step,
start: start,
end: end,
);
final query =
Options$Query$GetOverallCpuAndNetworkMetrics(variables: variables);
response = await client.query$GetOverallCpuAndNetworkMetrics(query);
if (response.hasException) {
print(response.exception.toString());
return GenericResult<ServerMetrics?>(
success: false,
data: null,
);
}
if (response.parsedData == null) {
return GenericResult<ServerMetrics?>(
success: false,
data: null,
);
}
if (response.parsedData?.monitoring.cpuUsage.overallUsage
is Fragment$MonitoringQueryError ||
response.parsedData?.monitoring.networkUsage.overallUsage
is Fragment$MonitoringQueryError) {
return GenericResult<ServerMetrics?>(
success: false,
data: null,
);
}
final metrics = ServerMetrics.fromGraphQL(
data: response.parsedData!.monitoring,
stepsInSecond: step,
start: start,
end: end,
);
return GenericResult<ServerMetrics?>(
success: true,
data: metrics,
);
} catch (e) {
print(e);
return GenericResult<ServerMetrics?>(
success: false,
data: null,
message: e.toString(),
);
}
}
Future<GenericResult<MemoryMetrics?>> getMemoryMetrics({
required final int step,
required final DateTime start,
required final DateTime end,
}) async {
QueryResult<Query$GetMemoryMetrics> response;
try {
final GraphQLClient client = await getClient();
final variables = Variables$Query$GetMemoryMetrics(
step: step,
start: start,
end: end,
);
final query = Options$Query$GetMemoryMetrics(variables: variables);
response = await client.query$GetMemoryMetrics(query);
if (response.hasException) {
print(response.exception.toString());
return GenericResult<MemoryMetrics?>(
success: false,
data: null,
);
}
if (response.parsedData == null) {
return GenericResult<MemoryMetrics?>(
success: false,
data: null,
);
}
if (response.parsedData?.monitoring.memoryUsage.overallUsage
is Fragment$MonitoringQueryError ||
response.parsedData?.monitoring.memoryUsage.averageUsageByService
is Fragment$MonitoringQueryError ||
response.parsedData?.monitoring.memoryUsage.maxUsageByService
is Fragment$MonitoringQueryError) {
return GenericResult<MemoryMetrics?>(
success: false,
data: null,
);
}
final metrics = MemoryMetrics.fromGraphQL(
data: response.parsedData!.monitoring,
stepsInSecond: step,
start: start,
end: end,
);
return GenericResult<MemoryMetrics?>(
success: true,
data: metrics,
);
} catch (e) {
print(e);
return GenericResult<MemoryMetrics?>(
success: false,
data: null,
message: e.toString(),
);
}
}
}

View file

@ -5,6 +5,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/graphql_api_map.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/logs.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/monitoring.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart';
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart';
@ -23,6 +24,7 @@ import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/models/json/recovery_token_status.dart';
import 'package:selfprivacy/logic/models/json/server_disk_volume.dart';
import 'package:selfprivacy/logic/models/json/server_job.dart';
import 'package:selfprivacy/logic/models/metrics.dart';
import 'package:selfprivacy/logic/models/server_logs.dart';
import 'package:selfprivacy/logic/models/service.dart';
import 'package:selfprivacy/logic/models/ssh_settings.dart';
@ -37,6 +39,7 @@ part 'services_api.dart';
part 'users_api.dart';
part 'volume_api.dart';
part 'logs_api.dart';
part 'monitoring_api.dart';
class ServerApi extends GraphQLApiMap
with
@ -46,7 +49,8 @@ class ServerApi extends GraphQLApiMap
ServicesApi,
UsersApi,
BackupsApi,
LogsApi {
LogsApi,
MonitoringApi {
ServerApi({
this.hasLogger = false,
this.isWithToken = true,

View file

@ -39,7 +39,16 @@ class MetricsCubit extends Cubit<MetricsState> {
void load(final Period period) async {
try {
final MetricsLoaded newState = await repository.getMetrics(period);
final MetricsLoaded newState = await repository.getServerMetrics(period);
timer = Timer(
Duration(seconds: newState.metrics.stepsInSecond.toInt()),
() => load(newState.period),
);
emit(newState);
return;
} catch (_) {}
try {
final MetricsLoaded newState = await repository.getLegacyMetrics(period);
timer = Timer(
Duration(seconds: newState.metrics.stepsInSecond.toInt()),
() => load(newState.period),

View file

@ -1,3 +1,5 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
@ -16,7 +18,66 @@ class MetricsUnsupportedException implements Exception {
}
class MetricsRepository {
Future<MetricsLoaded> getMetrics(final Period period) async {
static const String metricsSupportedVersion = '>=3.3.0';
Future<MetricsLoaded> getServerMetrics(final Period period) async {
final String? apiVersion =
getIt<ApiConnectionRepository>().apiData.apiVersion.data;
if (apiVersion == null) {
throw Exception('basis.network_error'.tr());
}
if (!VersionConstraint.parse(metricsSupportedVersion)
.allows(Version.parse(apiVersion))) {
throw Exception(
'basis.feature_unsupported_on_api_version'.tr(
namedArgs: {
'versionConstraint': metricsSupportedVersion,
'currentVersion': apiVersion,
},
),
);
}
final DateTime end = DateTime.now();
DateTime start;
switch (period) {
case Period.hour:
start = end.subtract(const Duration(hours: 1));
break;
case Period.day:
start = end.subtract(const Duration(days: 1));
break;
case Period.month:
start = end.subtract(const Duration(days: 15));
break;
}
final result = await getIt<ApiConnectionRepository>().api.getServerMetrics(
start: start,
end: end,
step: end.difference(start).inSeconds ~/ 120,
);
if (result.data == null || !result.success) {
throw MetricsLoadException('Metrics data is null');
}
final memoryResult =
await getIt<ApiConnectionRepository>().api.getMemoryMetrics(
start: start,
end: end,
step: end.difference(start).inSeconds ~/ 120,
);
return MetricsLoaded(
period: period,
metrics: result.data!,
source: MetricsDataSource.server,
memoryMetrics: memoryResult.data,
);
}
Future<MetricsLoaded> getLegacyMetrics(final Period period) async {
if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) {
throw MetricsUnsupportedException('Server Provider data is null');
}
@ -50,6 +111,7 @@ class MetricsRepository {
return MetricsLoaded(
period: period,
metrics: result.data!,
source: MetricsDataSource.legacy,
);
}
}

View file

@ -15,19 +15,31 @@ class MetricsLoading extends MetricsState {
List<Object?> get props => [period];
}
enum MetricsDataSource {
server,
legacy,
}
class MetricsLoaded extends MetricsState {
const MetricsLoaded({
required this.period,
required this.metrics,
required this.source,
this.memoryMetrics,
this.diskMetrics,
});
@override
final Period period;
final ServerMetrics metrics;
final MemoryMetrics? memoryMetrics;
final DiskMetrics? diskMetrics;
final MetricsDataSource source;
@override
List<Object?> get props => [period, metrics];
List<Object?> get props =>
[period, metrics, memoryMetrics, diskMetrics, source];
}
class MetricsUnsupported extends MetricsState {

View file

@ -1,3 +1,5 @@
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/monitoring.graphql.dart';
class TimeSeriesData {
TimeSeriesData(
this.secondsSinceEpoch,
@ -20,6 +22,52 @@ class ServerMetrics {
required this.end,
});
ServerMetrics.fromGraphQL({
required final Query$GetOverallCpuAndNetworkMetrics$monitoring data,
required final int stepsInSecond,
required final DateTime start,
required final DateTime end,
}) : this(
stepsInSecond: stepsInSecond,
cpu: (data.cpuUsage.overallUsage as Fragment$MonitoringValues)
.values
.map(
(final metric) => TimeSeriesData(
// Convert DateTime to seconds since epoch
metric.timestamp.millisecondsSinceEpoch ~/ 1000,
// Parse string as a float
double.parse(metric.value),
),
)
.toList(),
bandwidthIn:
(data.networkUsage.overallUsage as Fragment$MonitoringMetrics)
.metrics
.firstWhere((final element) => element.metricId == 'receive')
.values
.map(
(final metric) => TimeSeriesData(
metric.timestamp.millisecondsSinceEpoch ~/ 1000,
double.parse(metric.value),
),
)
.toList(),
bandwidthOut:
(data.networkUsage.overallUsage as Fragment$MonitoringMetrics)
.metrics
.firstWhere((final element) => element.metricId == 'transmit')
.values
.map(
(final metric) => TimeSeriesData(
metric.timestamp.millisecondsSinceEpoch ~/ 1000,
double.parse(metric.value),
),
)
.toList(),
start: start,
end: end,
);
final num stepsInSecond;
final List<TimeSeriesData> cpu;
final List<TimeSeriesData> bandwidthIn;
@ -28,3 +76,79 @@ class ServerMetrics {
final DateTime start;
final DateTime end;
}
class MemoryMetrics {
MemoryMetrics({
required this.stepsInSecond,
required this.overallMetrics,
required this.averageMetricsByService,
required this.maxMetricsByService,
required this.start,
required this.end,
});
MemoryMetrics.fromGraphQL({
required final Query$GetMemoryMetrics$monitoring data,
required final int stepsInSecond,
required final DateTime start,
required final DateTime end,
}) : this(
stepsInSecond: stepsInSecond,
overallMetrics:
(data.memoryUsage.overallUsage as Fragment$MonitoringValues)
.values
.map(
(final metric) => TimeSeriesData(
metric.timestamp.millisecondsSinceEpoch ~/ 1000,
double.parse(metric.value),
),
)
.toList(),
averageMetricsByService: Map.fromEntries(
(data.memoryUsage.averageUsageByService
as Fragment$MonitoringMetrics)
.metrics
.map(
(final metric) => MapEntry(
metric.metricId,
double.parse(metric.values.first.value),
),
),
),
maxMetricsByService: Map.fromEntries(
(data.memoryUsage.maxUsageByService as Fragment$MonitoringMetrics)
.metrics
.map(
(final metric) => MapEntry(
metric.metricId,
double.parse(metric.values.first.value),
),
),
),
start: start,
end: end,
);
final num stepsInSecond;
final List<TimeSeriesData> overallMetrics;
final Map<String, double> averageMetricsByService;
final Map<String, double> maxMetricsByService;
final DateTime start;
final DateTime end;
}
class DiskMetrics {
DiskMetrics({
required this.stepsInSecond,
required this.diskMetrics,
required this.start,
required this.end,
});
final num stepsInSecond;
final List<TimeSeriesData> diskMetrics;
final DateTime start;
final DateTime end;
}

View file

@ -39,6 +39,50 @@ class _Chart extends StatelessWidget {
),
),
const SizedBox(height: 8),
FilledCard(
clipped: false,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'resource_chart.memory'.tr(),
style: Theme.of(context).textTheme.titleMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
const SizedBox(height: 16),
Stack(
alignment: Alignment.center,
children: [
if (state is MetricsLoaded && state.memoryMetrics != null)
getMemoryChart(state),
AnimatedOpacity(
duration: const Duration(milliseconds: 200),
opacity: state is MetricsLoading ? 1 : 0,
child: const _GraphLoadingCardContent(),
),
],
),
const Divider(),
ListTile(
title: Text('resource_chart.view_usage_by_service'.tr()),
leading: Icon(
Icons.area_chart_outlined,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
onTap: () {
context.pushRoute(
const MemoryUsageByServiceRoute(),
);
},
),
],
),
),
),
const SizedBox(height: 8),
FilledCard(
clipped: false,
child: Padding(
@ -171,6 +215,23 @@ class _Chart extends StatelessWidget {
);
}
Widget getMemoryChart(final MetricsLoaded state) {
final data = state.memoryMetrics;
if (data == null) {
return const SizedBox();
}
return SizedBox(
height: 200,
child: MemoryChart(
data: data.overallMetrics,
period: state.period,
start: state.metrics.start,
),
);
}
Widget getBandwidthChart(final MetricsLoaded state) {
final ppsIn = state.metrics.bandwidthIn;
final ppsOut = state.metrics.bandwidthOut;

View file

@ -0,0 +1,179 @@
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/models/metrics.dart';
import 'package:selfprivacy/ui/pages/server_details/charts/bottom_title.dart';
class MemoryChart extends StatelessWidget {
const MemoryChart({
required this.data,
required this.period,
required this.start,
super.key,
});
final List<TimeSeriesData> data;
final Period period;
final DateTime start;
List<FlSpot> getSpots() {
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 = [];
for (final spot in touchedBarSpots) {
final value = spot.y;
final date = data[spot.x.toInt()].time;
res.add(
LineTooltipItem(
'${value.toStringAsFixed(2)}% at ${DateFormat('HH:mm dd.MM.yyyy').format(date)}',
TextStyle(
color: Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.bold,
),
),
);
}
return res;
},
),
),
lineBarsData: [
LineChartBarData(
spots: getSpots(),
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,
),
),
),
],
minY: 0,
// Maximal value of data by 100 step
maxY: 100,
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(),
data,
period,
),
style: Theme.of(context).textTheme.labelSmall?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
showTitles: true,
),
),
leftTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
rightTitles: const AxisTitles(
sideTitles: SideTitles(
showTitles: false,
),
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: 25,
verticalInterval: 40,
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 localValue = value - minValue;
final v = localValue / 20;
return v - v.floor() == 0;
}
}

View file

@ -0,0 +1,156 @@
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/svg.dart';
import 'package:selfprivacy/logic/bloc/services/services_bloc.dart';
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart';
import 'package:selfprivacy/logic/models/disk_size.dart';
import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/buttons/segmented_buttons.dart';
import 'package:selfprivacy/ui/helpers/empty_page_placeholder.dart';
import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart';
@RoutePage()
class MemoryUsageByServiceScreen extends StatelessWidget {
const MemoryUsageByServiceScreen({super.key});
@override
Widget build(final BuildContext context) => BlocProvider(
create: (final context) => MetricsCubit()..restart(),
child: const _MemoryUsageByServiceContents(),
);
}
class _MemoryUsageByServiceContents extends StatelessWidget {
const _MemoryUsageByServiceContents();
@override
Widget build(final BuildContext context) {
final MetricsCubit cubit = context.watch<MetricsCubit>();
final Period period = cubit.state.period;
final MetricsState state = cubit.state;
if (state is MetricsUnsupported ||
(state is MetricsLoaded && state.memoryMetrics == null)) {
return BrandHeroScreen(
heroTitle: 'resource_chart.memory'.tr(),
children: [
Center(
child: Center(
child: EmptyPagePlaceholder(
title: 'basis.error'.tr(),
iconData: Icons.error_outline_outlined,
),
),
),
],
);
}
if (state is MetricsLoading) {
return BrandHeroScreen(
heroTitle: 'resource_chart.memory'.tr(),
children: const [
Center(
child: CircularProgressIndicator(),
),
],
);
}
final averageUsageByServices =
(state as MetricsLoaded).memoryMetrics!.averageMetricsByService;
final maxUsageByServices = state.memoryMetrics!.maxMetricsByService;
// For each service, gather average and max usages
final List<Widget> children = [];
for (final slice in averageUsageByServices.keys.sorted()) {
final DiskSize averageUsage =
DiskSize(byte: averageUsageByServices[slice]?.toInt() ?? 0);
final DiskSize maxUsage =
DiskSize(byte: maxUsageByServices[slice]?.toInt() ?? 0);
String? serviceName;
Widget? icon;
if (slice == 'system') {
serviceName = 'resource_chart.system'.tr();
icon = const Icon(BrandIcons.server);
} else if (slice == 'user') {
serviceName = 'resource_chart.ssh_users'.tr();
icon = const Icon(BrandIcons.terminal);
} else {
final service = context
.read<ServicesBloc>()
.state
.getServiceById(slice.replaceAll('_', '-'));
serviceName = service?.displayName ?? slice;
icon = service?.svgIcon != null
? SvgPicture.string(
service!.svgIcon,
width: 22.0,
height: 24.0,
colorFilter: ColorFilter.mode(
Theme.of(context).colorScheme.onBackground,
BlendMode.srcIn,
),
)
: const Icon(BrandIcons.box);
}
if (serviceName == slice &&
averageUsage.byte == 0 &&
maxUsage.byte == 0) {
continue;
}
children.add(
ListTile(
title: Text(serviceName),
subtitle: Text(
'resource_chart.ram_usage'.tr(
namedArgs: {
'average': averageUsage.toString(),
'max': maxUsage.toString(),
},
),
),
dense: true,
leading: icon,
),
);
}
return BrandHeroScreen(
heroTitle: 'resource_chart.memory'.tr(),
children: [
SegmentedButtons(
isSelected: [
period == Period.month,
period == Period.day,
period == Period.hour,
],
onPressed: (final index) {
switch (index) {
case 0:
cubit.changePeriod(Period.month);
break;
case 1:
cubit.changePeriod(Period.day);
break;
case 2:
cubit.changePeriod(Period.hour);
break;
}
},
titles: [
'resource_chart.month'.tr(),
'resource_chart.day'.tr(),
'resource_chart.hour'.tr(),
],
),
...children,
],
);
}
}

View file

@ -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/layouts/brand_hero_screen.dart';
import 'package:selfprivacy/ui/pages/server_details/charts/cpu_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_storage/storage_card.dart';
import 'package:selfprivacy/ui/router/router.dart';

View file

@ -18,6 +18,7 @@ import 'package:selfprivacy/ui/pages/providers/providers.dart';
import 'package:selfprivacy/ui/pages/recovery_key/recovery_key.dart';
import 'package:selfprivacy/ui/pages/root_route.dart';
import 'package:selfprivacy/ui/pages/server_details/logs/logs_screen.dart';
import 'package:selfprivacy/ui/pages/server_details/memory_usage_by_service_screen.dart';
import 'package:selfprivacy/ui/pages/server_details/server_details_screen.dart';
import 'package:selfprivacy/ui/pages/server_details/server_settings_screen.dart';
import 'package:selfprivacy/ui/pages/server_storage/binds_migration/services_migration.dart';
@ -109,6 +110,7 @@ class RootRouter extends _$RootRouter {
AutoRoute(page: ServerSettingsRoute.page),
AutoRoute(page: ServerLogsRoute.page),
AutoRoute(page: TokensRoute.page),
AutoRoute(page: MemoryUsageByServiceRoute.page),
],
),
AutoRoute(page: ServicesMigrationRoute.page),
@ -166,6 +168,8 @@ String getRouteTitle(final String routeName) {
return 'storage.extending_volume_title';
case 'TokensRoute':
return 'tokens.title';
case 'MemoryUsageByServiceRoute':
return 'resource_chart.memory';
default:
return routeName;
}

View file

@ -84,6 +84,12 @@ abstract class _$RootRouter extends RootStackRouter {
child: const InitializingPage(),
);
},
MemoryUsageByServiceRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
child: const MemoryUsageByServiceScreen(),
);
},
MoreRoute.name: (routeData) {
return AutoRoutePage<dynamic>(
routeData: routeData,
@ -415,6 +421,20 @@ class InitializingRoute extends PageRouteInfo<void> {
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [MemoryUsageByServiceScreen]
class MemoryUsageByServiceRoute extends PageRouteInfo<void> {
const MemoryUsageByServiceRoute({List<PageRouteInfo>? children})
: super(
MemoryUsageByServiceRoute.name,
initialChildren: children,
);
static const String name = 'MemoryUsageByServiceRoute';
static const PageInfo<void> page = PageInfo<void>(name);
}
/// generated route for
/// [MorePage]
class MoreRoute extends PageRouteInfo<void> {