mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2025-03-11 09:14:10 +00:00
feat: CPU, Network and RAM stats from the server
This commit is contained in:
parent
8fe0de0c9e
commit
e065463ffb
16 changed files with 9747 additions and 6 deletions
assets/translations
lib
logic
api_maps/graphql_maps
schema
server_api
cubit/metrics
models
ui
pages/server_details
router
|
@ -139,7 +139,12 @@
|
||||||
"network_title": "Network Usage",
|
"network_title": "Network Usage",
|
||||||
"in": "In",
|
"in": "In",
|
||||||
"out": "Out",
|
"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": {
|
"server": {
|
||||||
"card_title": "Server",
|
"card_title": "Server",
|
||||||
|
|
68
lib/logic/api_maps/graphql_maps/schema/monitoring.graphql
Normal file
68
lib/logic/api_maps/graphql_maps/schema/monitoring.graphql
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8914
lib/logic/api_maps/graphql_maps/schema/monitoring.graphql.dart
Normal file
8914
lib/logic/api_maps/graphql_maps/schema/monitoring.graphql.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -280,7 +280,7 @@ type Monitoring {
|
||||||
}
|
}
|
||||||
|
|
||||||
type MonitoringMetric {
|
type MonitoringMetric {
|
||||||
id: String!
|
metricId: String!
|
||||||
values: [MonitoringValue!]!
|
values: [MonitoringValue!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
122
lib/logic/api_maps/graphql_maps/server_api/monitoring_api.dart
Normal file
122
lib/logic/api_maps/graphql_maps/server_api/monitoring_api.dart
Normal 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/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/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/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/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_api.graphql.dart';
|
||||||
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.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/recovery_token_status.dart';
|
||||||
import 'package:selfprivacy/logic/models/json/server_disk_volume.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/json/server_job.dart';
|
||||||
|
import 'package:selfprivacy/logic/models/metrics.dart';
|
||||||
import 'package:selfprivacy/logic/models/server_logs.dart';
|
import 'package:selfprivacy/logic/models/server_logs.dart';
|
||||||
import 'package:selfprivacy/logic/models/service.dart';
|
import 'package:selfprivacy/logic/models/service.dart';
|
||||||
import 'package:selfprivacy/logic/models/ssh_settings.dart';
|
import 'package:selfprivacy/logic/models/ssh_settings.dart';
|
||||||
|
@ -37,6 +39,7 @@ part 'services_api.dart';
|
||||||
part 'users_api.dart';
|
part 'users_api.dart';
|
||||||
part 'volume_api.dart';
|
part 'volume_api.dart';
|
||||||
part 'logs_api.dart';
|
part 'logs_api.dart';
|
||||||
|
part 'monitoring_api.dart';
|
||||||
|
|
||||||
class ServerApi extends GraphQLApiMap
|
class ServerApi extends GraphQLApiMap
|
||||||
with
|
with
|
||||||
|
@ -46,7 +49,8 @@ class ServerApi extends GraphQLApiMap
|
||||||
ServicesApi,
|
ServicesApi,
|
||||||
UsersApi,
|
UsersApi,
|
||||||
BackupsApi,
|
BackupsApi,
|
||||||
LogsApi {
|
LogsApi,
|
||||||
|
MonitoringApi {
|
||||||
ServerApi({
|
ServerApi({
|
||||||
this.hasLogger = false,
|
this.hasLogger = false,
|
||||||
this.isWithToken = true,
|
this.isWithToken = true,
|
||||||
|
|
|
@ -39,7 +39,16 @@ class MetricsCubit extends Cubit<MetricsState> {
|
||||||
|
|
||||||
void load(final Period period) async {
|
void load(final Period period) async {
|
||||||
try {
|
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(
|
timer = Timer(
|
||||||
Duration(seconds: newState.metrics.stepsInSecond.toInt()),
|
Duration(seconds: newState.metrics.stepsInSecond.toInt()),
|
||||||
() => load(newState.period),
|
() => load(newState.period),
|
||||||
|
|
|
@ -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/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
import 'package:selfprivacy/logic/common_enum/common_enum.dart';
|
||||||
|
|
||||||
|
@ -16,7 +18,66 @@ class MetricsUnsupportedException implements Exception {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetricsRepository {
|
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)) {
|
if (!(ProvidersController.currentServerProvider?.isAuthorized ?? false)) {
|
||||||
throw MetricsUnsupportedException('Server Provider data is null');
|
throw MetricsUnsupportedException('Server Provider data is null');
|
||||||
}
|
}
|
||||||
|
@ -50,6 +111,7 @@ class MetricsRepository {
|
||||||
return MetricsLoaded(
|
return MetricsLoaded(
|
||||||
period: period,
|
period: period,
|
||||||
metrics: result.data!,
|
metrics: result.data!,
|
||||||
|
source: MetricsDataSource.legacy,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,19 +15,31 @@ class MetricsLoading extends MetricsState {
|
||||||
List<Object?> get props => [period];
|
List<Object?> get props => [period];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MetricsDataSource {
|
||||||
|
server,
|
||||||
|
legacy,
|
||||||
|
}
|
||||||
|
|
||||||
class MetricsLoaded extends MetricsState {
|
class MetricsLoaded extends MetricsState {
|
||||||
const MetricsLoaded({
|
const MetricsLoaded({
|
||||||
required this.period,
|
required this.period,
|
||||||
required this.metrics,
|
required this.metrics,
|
||||||
|
required this.source,
|
||||||
|
this.memoryMetrics,
|
||||||
|
this.diskMetrics,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final Period period;
|
final Period period;
|
||||||
|
|
||||||
final ServerMetrics metrics;
|
final ServerMetrics metrics;
|
||||||
|
final MemoryMetrics? memoryMetrics;
|
||||||
|
final DiskMetrics? diskMetrics;
|
||||||
|
final MetricsDataSource source;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [period, metrics];
|
List<Object?> get props =>
|
||||||
|
[period, metrics, memoryMetrics, diskMetrics, source];
|
||||||
}
|
}
|
||||||
|
|
||||||
class MetricsUnsupported extends MetricsState {
|
class MetricsUnsupported extends MetricsState {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/monitoring.graphql.dart';
|
||||||
|
|
||||||
class TimeSeriesData {
|
class TimeSeriesData {
|
||||||
TimeSeriesData(
|
TimeSeriesData(
|
||||||
this.secondsSinceEpoch,
|
this.secondsSinceEpoch,
|
||||||
|
@ -20,6 +22,52 @@ class ServerMetrics {
|
||||||
required this.end,
|
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 num stepsInSecond;
|
||||||
final List<TimeSeriesData> cpu;
|
final List<TimeSeriesData> cpu;
|
||||||
final List<TimeSeriesData> bandwidthIn;
|
final List<TimeSeriesData> bandwidthIn;
|
||||||
|
@ -28,3 +76,79 @@ class ServerMetrics {
|
||||||
final DateTime start;
|
final DateTime start;
|
||||||
final DateTime end;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -39,6 +39,50 @@ class _Chart extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
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(
|
FilledCard(
|
||||||
clipped: false,
|
clipped: false,
|
||||||
child: Padding(
|
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) {
|
Widget getBandwidthChart(final MetricsLoaded state) {
|
||||||
final ppsIn = state.metrics.bandwidthIn;
|
final ppsIn = state.metrics.bandwidthIn;
|
||||||
final ppsOut = state.metrics.bandwidthOut;
|
final ppsOut = state.metrics.bandwidthOut;
|
||||||
|
|
179
lib/ui/pages/server_details/charts/memory_chart.dart
Normal file
179
lib/ui/pages/server_details/charts/memory_chart.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
156
lib/ui/pages/server_details/memory_usage_by_service_screen.dart
Normal file
156
lib/ui/pages/server_details/memory_usage_by_service_screen.dart
Normal 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,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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/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';
|
||||||
import 'package:selfprivacy/ui/router/router.dart';
|
import 'package:selfprivacy/ui/router/router.dart';
|
||||||
|
|
|
@ -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/recovery_key/recovery_key.dart';
|
||||||
import 'package:selfprivacy/ui/pages/root_route.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/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_details_screen.dart';
|
||||||
import 'package:selfprivacy/ui/pages/server_details/server_settings_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';
|
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: ServerSettingsRoute.page),
|
||||||
AutoRoute(page: ServerLogsRoute.page),
|
AutoRoute(page: ServerLogsRoute.page),
|
||||||
AutoRoute(page: TokensRoute.page),
|
AutoRoute(page: TokensRoute.page),
|
||||||
|
AutoRoute(page: MemoryUsageByServiceRoute.page),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(page: ServicesMigrationRoute.page),
|
AutoRoute(page: ServicesMigrationRoute.page),
|
||||||
|
@ -166,6 +168,8 @@ String getRouteTitle(final String routeName) {
|
||||||
return 'storage.extending_volume_title';
|
return 'storage.extending_volume_title';
|
||||||
case 'TokensRoute':
|
case 'TokensRoute':
|
||||||
return 'tokens.title';
|
return 'tokens.title';
|
||||||
|
case 'MemoryUsageByServiceRoute':
|
||||||
|
return 'resource_chart.memory';
|
||||||
default:
|
default:
|
||||||
return routeName;
|
return routeName;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,6 +84,12 @@ abstract class _$RootRouter extends RootStackRouter {
|
||||||
child: const InitializingPage(),
|
child: const InitializingPage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
MemoryUsageByServiceRoute.name: (routeData) {
|
||||||
|
return AutoRoutePage<dynamic>(
|
||||||
|
routeData: routeData,
|
||||||
|
child: const MemoryUsageByServiceScreen(),
|
||||||
|
);
|
||||||
|
},
|
||||||
MoreRoute.name: (routeData) {
|
MoreRoute.name: (routeData) {
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
|
@ -415,6 +421,20 @@ class InitializingRoute extends PageRouteInfo<void> {
|
||||||
static const PageInfo<void> page = PageInfo<void>(name);
|
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
|
/// generated route for
|
||||||
/// [MorePage]
|
/// [MorePage]
|
||||||
class MoreRoute extends PageRouteInfo<void> {
|
class MoreRoute extends PageRouteInfo<void> {
|
||||||
|
|
Loading…
Add table
Reference in a new issue