feat: Allow viewing service logs from the service screen

This commit is contained in:
Inex Code 2024-07-30 01:47:27 +03:00
parent 74eb1135df
commit 894d23bb7c
14 changed files with 237 additions and 48 deletions

View file

@ -370,7 +370,8 @@
"invalid_input": "Invalid input", "invalid_input": "Invalid input",
"create_job": "Create job", "create_job": "Create job",
"update_job": "Update job", "update_job": "Update job",
"wait_for_jobs": "Server is busy with other jobs. Please wait until they are finished." "wait_for_jobs": "Server is busy with other jobs. Please wait until they are finished.",
"logs": "Service logs"
}, },
"mail": { "mail": {
"login_info": "Use username and password from users tab. IMAP port is 143 with STARTTLS, SMTP port is 587 with STARTTLS." "login_info": "Use username and password from users tab. IMAP port is 143 with STARTTLS, SMTP port is 587 with STARTTLS."

View file

@ -7,9 +7,9 @@ fragment LogEntry on LogEntry {
cursor cursor
} }
query Logs($limit: Int!, $upCursor: String, $downCursor: String) { query Logs($limit: Int!, $upCursor: String, $downCursor: String, $filterBySlice: String) {
logs { logs {
paginated(limit: $limit, upCursor: $upCursor, downCursor: $downCursor) { paginated(limit: $limit, upCursor: $upCursor, downCursor: $downCursor, filterBySlice: $filterBySlice) {
pageMeta { pageMeta {
upCursor upCursor
downCursor downCursor

View file

@ -329,11 +329,13 @@ class Variables$Query$Logs {
required int limit, required int limit,
String? upCursor, String? upCursor,
String? downCursor, String? downCursor,
String? filterBySlice,
}) => }) =>
Variables$Query$Logs._({ Variables$Query$Logs._({
r'limit': limit, r'limit': limit,
if (upCursor != null) r'upCursor': upCursor, if (upCursor != null) r'upCursor': upCursor,
if (downCursor != null) r'downCursor': downCursor, if (downCursor != null) r'downCursor': downCursor,
if (filterBySlice != null) r'filterBySlice': filterBySlice,
}); });
Variables$Query$Logs._(this._$data); Variables$Query$Logs._(this._$data);
@ -350,6 +352,10 @@ class Variables$Query$Logs {
final l$downCursor = data['downCursor']; final l$downCursor = data['downCursor'];
result$data['downCursor'] = (l$downCursor as String?); result$data['downCursor'] = (l$downCursor as String?);
} }
if (data.containsKey('filterBySlice')) {
final l$filterBySlice = data['filterBySlice'];
result$data['filterBySlice'] = (l$filterBySlice as String?);
}
return Variables$Query$Logs._(result$data); return Variables$Query$Logs._(result$data);
} }
@ -361,6 +367,8 @@ class Variables$Query$Logs {
String? get downCursor => (_$data['downCursor'] as String?); String? get downCursor => (_$data['downCursor'] as String?);
String? get filterBySlice => (_$data['filterBySlice'] as String?);
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
final result$data = <String, dynamic>{}; final result$data = <String, dynamic>{};
final l$limit = limit; final l$limit = limit;
@ -373,6 +381,10 @@ class Variables$Query$Logs {
final l$downCursor = downCursor; final l$downCursor = downCursor;
result$data['downCursor'] = l$downCursor; result$data['downCursor'] = l$downCursor;
} }
if (_$data.containsKey('filterBySlice')) {
final l$filterBySlice = filterBySlice;
result$data['filterBySlice'] = l$filterBySlice;
}
return result$data; return result$data;
} }
@ -413,6 +425,15 @@ class Variables$Query$Logs {
if (l$downCursor != lOther$downCursor) { if (l$downCursor != lOther$downCursor) {
return false; return false;
} }
final l$filterBySlice = filterBySlice;
final lOther$filterBySlice = other.filterBySlice;
if (_$data.containsKey('filterBySlice') !=
other._$data.containsKey('filterBySlice')) {
return false;
}
if (l$filterBySlice != lOther$filterBySlice) {
return false;
}
return true; return true;
} }
@ -421,10 +442,12 @@ class Variables$Query$Logs {
final l$limit = limit; final l$limit = limit;
final l$upCursor = upCursor; final l$upCursor = upCursor;
final l$downCursor = downCursor; final l$downCursor = downCursor;
final l$filterBySlice = filterBySlice;
return Object.hashAll([ return Object.hashAll([
l$limit, l$limit,
_$data.containsKey('upCursor') ? l$upCursor : const {}, _$data.containsKey('upCursor') ? l$upCursor : const {},
_$data.containsKey('downCursor') ? l$downCursor : const {}, _$data.containsKey('downCursor') ? l$downCursor : const {},
_$data.containsKey('filterBySlice') ? l$filterBySlice : const {},
]); ]);
} }
} }
@ -442,6 +465,7 @@ abstract class CopyWith$Variables$Query$Logs<TRes> {
int? limit, int? limit,
String? upCursor, String? upCursor,
String? downCursor, String? downCursor,
String? filterBySlice,
}); });
} }
@ -462,12 +486,15 @@ class _CopyWithImpl$Variables$Query$Logs<TRes>
Object? limit = _undefined, Object? limit = _undefined,
Object? upCursor = _undefined, Object? upCursor = _undefined,
Object? downCursor = _undefined, Object? downCursor = _undefined,
Object? filterBySlice = _undefined,
}) => }) =>
_then(Variables$Query$Logs._({ _then(Variables$Query$Logs._({
..._instance._$data, ..._instance._$data,
if (limit != _undefined && limit != null) 'limit': (limit as int), if (limit != _undefined && limit != null) 'limit': (limit as int),
if (upCursor != _undefined) 'upCursor': (upCursor as String?), if (upCursor != _undefined) 'upCursor': (upCursor as String?),
if (downCursor != _undefined) 'downCursor': (downCursor as String?), if (downCursor != _undefined) 'downCursor': (downCursor as String?),
if (filterBySlice != _undefined)
'filterBySlice': (filterBySlice as String?),
})); }));
} }
@ -481,6 +508,7 @@ class _CopyWithStubImpl$Variables$Query$Logs<TRes>
int? limit, int? limit,
String? upCursor, String? upCursor,
String? downCursor, String? downCursor,
String? filterBySlice,
}) => }) =>
_res; _res;
} }
@ -645,6 +673,15 @@ const documentNodeQueryLogs = DocumentNode(definitions: [
defaultValue: DefaultValueNode(value: null), defaultValue: DefaultValueNode(value: null),
directives: [], directives: [],
), ),
VariableDefinitionNode(
variable: VariableNode(name: NameNode(value: 'filterBySlice')),
type: NamedTypeNode(
name: NameNode(value: 'String'),
isNonNull: false,
),
defaultValue: DefaultValueNode(value: null),
directives: [],
),
], ],
directives: [], directives: [],
selectionSet: SelectionSetNode(selections: [ selectionSet: SelectionSetNode(selections: [
@ -670,6 +707,10 @@ const documentNodeQueryLogs = DocumentNode(definitions: [
name: NameNode(value: 'downCursor'), name: NameNode(value: 'downCursor'),
value: VariableNode(name: NameNode(value: 'downCursor')), value: VariableNode(name: NameNode(value: 'downCursor')),
), ),
ArgumentNode(
name: NameNode(value: 'filterBySlice'),
value: VariableNode(name: NameNode(value: 'filterBySlice')),
),
], ],
directives: [], directives: [],
selectionSet: SelectionSetNode(selections: [ selectionSet: SelectionSetNode(selections: [

View file

@ -146,6 +146,13 @@ interface ConfigItem {
type: String! type: String!
} }
type CpuMonitoring {
start: DateTime
end: DateTime
step: Int!
overallUsage: MonitoringValuesResult!
}
"""Date with time (isoformat)""" """Date with time (isoformat)"""
scalar DateTime scalar DateTime
@ -156,6 +163,13 @@ type DeviceApiTokenMutationReturn implements MutationReturnInterface {
token: String token: String
} }
type DiskMonitoring {
start: DateTime
end: DateTime
step: Int!
overallUsage: MonitoringMetricsResult!
}
enum DnsProvider { enum DnsProvider {
CLOUDFLARE CLOUDFLARE
DIGITALOCEAN DIGITALOCEAN
@ -210,9 +224,9 @@ input InitializeRepositoryInput {
} }
""" """
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). The `JSON` scalar type represents JSON values as specified by [ECMA-404](https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf).
""" """
scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") scalar JSON @specifiedBy(url: "https://ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf")
type Job { type Job {
getJobs: [ApiJob!]! getJobs: [ApiJob!]!
@ -233,7 +247,7 @@ type LogEntry {
} }
type Logs { type Logs {
paginated(limit: Int! = 20, upCursor: String = null, downCursor: String = null): PaginatedEntries! paginated(limit: Int! = 20, upCursor: String = null, downCursor: String = null, filterBySlice: String = null, filterByUnit: String = null): PaginatedEntries!
} }
type LogsPageMeta { type LogsPageMeta {
@ -241,6 +255,15 @@ type LogsPageMeta {
downCursor: String downCursor: String
} }
type MemoryMonitoring {
start: DateTime
end: DateTime
step: Int!
overallUsage: MonitoringValuesResult!
averageUsageByService: MonitoringMetricsResult!
maxUsageByService: MonitoringMetricsResult!
}
input MigrateToBindsInput { input MigrateToBindsInput {
emailBlockDevice: String! emailBlockDevice: String!
bitwardenBlockDevice: String! bitwardenBlockDevice: String!
@ -249,6 +272,39 @@ input MigrateToBindsInput {
pleromaBlockDevice: String! pleromaBlockDevice: String!
} }
type Monitoring {
cpuUsage(start: DateTime = null, end: DateTime = null, step: Int! = 60): CpuMonitoring!
memoryUsage(start: DateTime = null, end: DateTime = null, step: Int! = 60): MemoryMonitoring!
diskUsage(start: DateTime = null, end: DateTime = null, step: Int! = 60): DiskMonitoring!
networkUsage(start: DateTime = null, end: DateTime = null, step: Int! = 60): NetworkMonitoring!
}
type MonitoringMetric {
id: String!
values: [MonitoringValue!]!
}
type MonitoringMetrics {
metrics: [MonitoringMetric!]!
}
union MonitoringMetricsResult = MonitoringMetrics | MonitoringQueryError
type MonitoringQueryError {
error: String!
}
type MonitoringValue {
timestamp: DateTime!
value: String!
}
type MonitoringValues {
values: [MonitoringValue!]!
}
union MonitoringValuesResult = MonitoringValues | MonitoringQueryError
input MoveServiceInput { input MoveServiceInput {
serviceId: String! serviceId: String!
location: String! location: String!
@ -301,6 +357,13 @@ interface MutationReturnInterface {
code: Int! code: Int!
} }
type NetworkMonitoring {
start: DateTime
end: DateTime
step: Int!
overallUsage: MonitoringMetricsResult!
}
type PaginatedEntries { type PaginatedEntries {
"""Metadata to aid in pagination.""" """Metadata to aid in pagination."""
pageMeta: LogsPageMeta! pageMeta: LogsPageMeta!
@ -318,6 +381,7 @@ type Query {
jobs: Job! jobs: Job!
services: Services! services: Services!
backup: Backup! backup: Backup!
monitoring: Monitoring!
} }
input RecoveryKeyLimitsInput { input RecoveryKeyLimitsInput {
@ -357,6 +421,7 @@ type Service {
isMovable: Boolean! isMovable: Boolean!
isRequired: Boolean! isRequired: Boolean!
isEnabled: Boolean! isEnabled: Boolean!
isInstalled: Boolean!
canBeBackedUp: Boolean! canBeBackedUp: Boolean!
backupDescription: String! backupDescription: String!
status: ServiceStatusEnum! status: ServiceStatusEnum!

View file

@ -2289,5 +2289,13 @@ const possibleTypesMap = <String, Set<String>>{
'EnumConfigItem', 'EnumConfigItem',
'StringConfigItem', 'StringConfigItem',
}, },
'MonitoringMetricsResult': {
'MonitoringMetrics',
'MonitoringQueryError',
},
'MonitoringValuesResult': {
'MonitoringValues',
'MonitoringQueryError',
},
'StorageUsageInterface': {'ServiceStorageUsage'}, 'StorageUsageInterface': {'ServiceStorageUsage'},
}; };

View file

@ -5,6 +5,7 @@ mixin LogsApi on GraphQLApiMap {
required final int limit, required final int limit,
final String? upCursor, final String? upCursor,
final String? downCursor, final String? downCursor,
final String? slice,
}) async { }) async {
QueryResult<Query$Logs> response; QueryResult<Query$Logs> response;
List<ServerLogEntry> logsList = []; List<ServerLogEntry> logsList = [];
@ -17,6 +18,7 @@ mixin LogsApi on GraphQLApiMap {
upCursor: upCursor, upCursor: upCursor,
downCursor: downCursor, downCursor: downCursor,
limit: limit, limit: limit,
filterBySlice: slice,
); );
final query = Options$Query$Logs(variables: variables); final query = Options$Query$Logs(variables: variables);
response = await client.query$Logs(query); response = await client.query$Logs(query);

View file

@ -15,16 +15,20 @@ class ServerLogsBloc extends Bloc<ServerLogsEvent, ServerLogsState> {
ServerLogsBloc() : super(ServerLogsInitial()) { ServerLogsBloc() : super(ServerLogsInitial()) {
on<ServerLogsFetch>((final event, final emit) async { on<ServerLogsFetch>((final event, final emit) async {
emit(ServerLogsLoading()); emit(ServerLogsLoading());
final String? slice = event.serviceId != null
? '${event.serviceId?.replaceAll('-', '_')}.slice'
: null;
try { try {
final (logsData, meta) = await _getLogs(limit: 50); final (logsData, meta) = await _getLogs(limit: 50, slice: slice);
emit( emit(
ServerLogsLoaded( ServerLogsLoaded(
logsData.sorted( oldEntries: logsData.sorted(
(final a, final b) => b.timestamp.compareTo(a.timestamp), (final a, final b) => b.timestamp.compareTo(a.timestamp),
), ),
List<ServerLogEntry>.empty(growable: true), newEntries: List<ServerLogEntry>.empty(growable: true),
meta, meta: meta,
false, loadingMore: false,
slice: slice,
), ),
); );
if (_apiLogsSubscription != null) { if (_apiLogsSubscription != null) {
@ -49,17 +53,21 @@ class ServerLogsBloc extends Bloc<ServerLogsEvent, ServerLogsState> {
!currentState.loadingMore && !currentState.loadingMore &&
currentState.meta.upCursor != null) { currentState.meta.upCursor != null) {
try { try {
final (logsData, meta) = final (logsData, meta) = await _getLogs(
await _getLogs(limit: 50, downCursor: currentState.meta.upCursor); limit: 50,
downCursor: currentState.meta.upCursor,
slice: currentState.slice,
);
final allEntries = currentState.oldEntries final allEntries = currentState.oldEntries
..addAll(logsData) ..addAll(logsData)
..sort((final a, final b) => b.timestamp.compareTo(a.timestamp)); ..sort((final a, final b) => b.timestamp.compareTo(a.timestamp));
emit( emit(
ServerLogsLoaded( ServerLogsLoaded(
allEntries.toSet().toList(), oldEntries: allEntries.toSet().toList(),
currentState.newEntries, newEntries: currentState.newEntries,
meta, meta: meta,
false, loadingMore: false,
slice: currentState.slice,
), ),
); );
} catch (e) { } catch (e) {
@ -71,6 +79,10 @@ class ServerLogsBloc extends Bloc<ServerLogsEvent, ServerLogsState> {
on<ServerLogsGotNewEntry>((final event, final emit) { on<ServerLogsGotNewEntry>((final event, final emit) {
final currentState = state; final currentState = state;
if (currentState is ServerLogsLoaded) { if (currentState is ServerLogsLoaded) {
if (currentState.slice != null &&
event.entry.systemdSlice != currentState.slice) {
return;
}
final allEntries = currentState.newEntries final allEntries = currentState.newEntries
..add(event.entry) ..add(event.entry)
..sort( ..sort(
@ -78,10 +90,11 @@ class ServerLogsBloc extends Bloc<ServerLogsEvent, ServerLogsState> {
); );
emit( emit(
ServerLogsLoaded( ServerLogsLoaded(
currentState.oldEntries, oldEntries: currentState.oldEntries,
allEntries.toSet().toList(), newEntries: allEntries.toSet().toList(),
currentState.meta, meta: currentState.meta,
currentState.loadingMore, loadingMore: currentState.loadingMore,
slice: currentState.slice,
), ),
); );
} }
@ -103,6 +116,7 @@ class ServerLogsBloc extends Bloc<ServerLogsEvent, ServerLogsState> {
// All entries returned will be greater than this cursor. Sets lower bound on results. // All entries returned will be greater than this cursor. Sets lower bound on results.
final String? downCursor, final String? downCursor,
// Only one cursor can be set at a time. // Only one cursor can be set at a time.
final String? slice,
}) { }) {
final String? apiVersion = final String? apiVersion =
getIt<ApiConnectionRepository>().apiData.apiVersion.data; getIt<ApiConnectionRepository>().apiData.apiVersion.data;
@ -124,6 +138,7 @@ class ServerLogsBloc extends Bloc<ServerLogsEvent, ServerLogsState> {
limit: limit, limit: limit,
upCursor: upCursor, upCursor: upCursor,
downCursor: downCursor, downCursor: downCursor,
slice: slice,
); );
} }

View file

@ -5,6 +5,10 @@ sealed class ServerLogsEvent extends Equatable {
} }
final class ServerLogsFetch extends ServerLogsEvent { final class ServerLogsFetch extends ServerLogsEvent {
const ServerLogsFetch({this.serviceId});
final String? serviceId;
@override @override
List<Object> get props => []; List<Object> get props => [];
} }

View file

@ -15,18 +15,20 @@ final class ServerLogsLoading extends ServerLogsState {
} }
final class ServerLogsLoaded extends ServerLogsState { final class ServerLogsLoaded extends ServerLogsState {
ServerLogsLoaded( ServerLogsLoaded({
this.oldEntries, required this.oldEntries,
this.newEntries, required this.newEntries,
this.meta, required this.meta,
this.loadingMore, required this.loadingMore,
) : _lastCursor = newEntries.isEmpty ? '' : newEntries.first.cursor; this.slice,
}) : _lastCursor = newEntries.isEmpty ? '' : newEntries.first.cursor;
final List<ServerLogEntry> oldEntries; final List<ServerLogEntry> oldEntries;
final List<ServerLogEntry> newEntries; final List<ServerLogEntry> newEntries;
final ServerLogsPageMeta meta; final ServerLogsPageMeta meta;
final bool loadingMore; final bool loadingMore;
final String _lastCursor; final String _lastCursor;
final String? slice;
List<String> get systemdUnits => oldEntries List<String> get systemdUnits => oldEntries
.map((final entry) => entry.systemdUnit ?? 'kernel') .map((final entry) => entry.systemdUnit ?? 'kernel')
@ -56,7 +58,7 @@ final class ServerLogsLoaded extends ServerLogsState {
} }
@override @override
List<Object> get props => [oldEntries, newEntries, meta, _lastCursor]; List<Object?> get props => [oldEntries, newEntries, meta, _lastCursor, slice];
} }
final class ServerLogsError extends ServerLogsState { final class ServerLogsError extends ServerLogsState {

View file

@ -6,7 +6,7 @@ class EmptyPagePlaceholder extends StatelessWidget {
const EmptyPagePlaceholder({ const EmptyPagePlaceholder({
required this.title, required this.title,
required this.iconData, required this.iconData,
required this.description, this.description,
this.showReadyCard = false, this.showReadyCard = false,
super.key, super.key,
}); });
@ -14,7 +14,7 @@ class EmptyPagePlaceholder extends StatelessWidget {
final bool showReadyCard; final bool showReadyCard;
final IconData iconData; final IconData iconData;
final String title; final String title;
final String description; final String? description;
@override @override
Widget build(final BuildContext context) => showReadyCard Widget build(final BuildContext context) => showReadyCard
@ -54,7 +54,7 @@ class _ContentWidget extends StatelessWidget {
final IconData iconData; final IconData iconData;
final String title; final String title;
final String description; final String? description;
@override @override
Widget build(final BuildContext context) => Container( Widget build(final BuildContext context) => Container(
@ -76,9 +76,10 @@ class _ContentWidget extends StatelessWidget {
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const Gap(8), if (description != null) const Gap(8),
if (description != null)
Text( Text(
description, description!,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall?.copyWith( style: Theme.of(context).textTheme.titleSmall?.copyWith(
color: Theme.of(context).colorScheme.onBackground, color: Theme.of(context).colorScheme.onBackground,

View file

@ -10,7 +10,9 @@ import 'package:selfprivacy/utils/platform_adapter.dart';
@RoutePage() @RoutePage()
class ServerLogsScreen extends StatefulWidget { class ServerLogsScreen extends StatefulWidget {
const ServerLogsScreen({super.key}); const ServerLogsScreen({this.serviceId, super.key});
final String? serviceId;
@override @override
State<ServerLogsScreen> createState() => _ServerLogsScreenState(); State<ServerLogsScreen> createState() => _ServerLogsScreenState();
@ -27,7 +29,7 @@ class _ServerLogsScreenState extends State<ServerLogsScreen> {
super.initState(); super.initState();
_serverLogsBloc = BlocProvider.of<ServerLogsBloc>(context); _serverLogsBloc = BlocProvider.of<ServerLogsBloc>(context);
_scrollController.addListener(_onScroll); _scrollController.addListener(_onScroll);
_serverLogsBloc.add(ServerLogsFetch()); _serverLogsBloc.add(ServerLogsFetch(serviceId: widget.serviceId));
} }
@override @override
@ -82,7 +84,7 @@ class _ServerLogsScreenState extends State<ServerLogsScreen> {
const Key centerKey = ValueKey<String>('server-logs-center-key'); const Key centerKey = ValueKey<String>('server-logs-center-key');
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text('server.logs'.tr()), title: Text(widget.serviceId == null ? 'server.logs'.tr() : 'service_page.logs'.tr()),
), ),
endDrawer: BlocBuilder<ServerLogsBloc, ServerLogsState>( endDrawer: BlocBuilder<ServerLogsBloc, ServerLogsState>(
builder: (final context, final state) { builder: (final context, final state) {
@ -107,6 +109,14 @@ class _ServerLogsScreenState extends State<ServerLogsScreen> {
_selectedSystemdUnit == null _selectedSystemdUnit == null
? state.oldEntries ? state.oldEntries
: state.oldEntriesForUnit(_selectedSystemdUnit!); : state.oldEntriesForUnit(_selectedSystemdUnit!);
if (filteredOldLogs.isEmpty && filteredNewLogs.isEmpty) {
return Center(
child: EmptyPagePlaceholder(
title: 'server.logs_empty'.tr(),
iconData: Icons.info_outline,
),
);
}
return CustomScrollView( return CustomScrollView(
center: centerKey, center: centerKey,
controller: _scrollController, controller: _scrollController,

View file

@ -83,7 +83,7 @@ class _ServerDetailsScreenState extends State<ServerDetailsScreen>
ListTile( ListTile(
title: Text('server.logs'.tr()), title: Text('server.logs'.tr()),
leading: const Icon(Icons.manage_search_outlined), leading: const Icon(Icons.manage_search_outlined),
onTap: () => context.pushRoute(const ServerLogsRoute()), onTap: () => context.pushRoute(ServerLogsRoute()),
), ),
const Divider(height: 32), const Divider(height: 32),
Text( Text(

View file

@ -172,6 +172,17 @@ class _ServicePageState extends State<ServicePage> {
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
), ),
ListTile(
iconColor: Theme.of(context).colorScheme.onBackground,
onTap: () => context.pushRoute(
ServerLogsRoute(serviceId: service.id),
),
leading: const Icon(Icons.manage_search_outlined),
title: Text(
'service_page.logs'.tr(),
style: Theme.of(context).textTheme.titleMedium,
),
),
], ],
); );
} }

View file

@ -133,9 +133,14 @@ abstract class _$RootRouter extends RootStackRouter {
); );
}, },
ServerLogsRoute.name: (routeData) { ServerLogsRoute.name: (routeData) {
final args = routeData.argsAs<ServerLogsRouteArgs>(
orElse: () => const ServerLogsRouteArgs());
return AutoRoutePage<dynamic>( return AutoRoutePage<dynamic>(
routeData: routeData, routeData: routeData,
child: const ServerLogsScreen(), child: ServerLogsScreen(
serviceId: args.serviceId,
key: args.key,
),
); );
}, },
ServerSettingsRoute.name: (routeData) { ServerSettingsRoute.name: (routeData) {
@ -524,16 +529,40 @@ class ServerDetailsRoute extends PageRouteInfo<void> {
/// generated route for /// generated route for
/// [ServerLogsScreen] /// [ServerLogsScreen]
class ServerLogsRoute extends PageRouteInfo<void> { class ServerLogsRoute extends PageRouteInfo<ServerLogsRouteArgs> {
const ServerLogsRoute({List<PageRouteInfo>? children}) ServerLogsRoute({
: super( String? serviceId,
Key? key,
List<PageRouteInfo>? children,
}) : super(
ServerLogsRoute.name, ServerLogsRoute.name,
args: ServerLogsRouteArgs(
serviceId: serviceId,
key: key,
),
initialChildren: children, initialChildren: children,
); );
static const String name = 'ServerLogsRoute'; static const String name = 'ServerLogsRoute';
static const PageInfo<void> page = PageInfo<void>(name); static const PageInfo<ServerLogsRouteArgs> page =
PageInfo<ServerLogsRouteArgs>(name);
}
class ServerLogsRouteArgs {
const ServerLogsRouteArgs({
this.serviceId,
this.key,
});
final String? serviceId;
final Key? key;
@override
String toString() {
return 'ServerLogsRouteArgs{serviceId: $serviceId, key: $key}';
}
} }
/// generated route for /// generated route for