mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-11-13 04:03:17 +00:00
feat: Allow viewing service logs from the service screen
This commit is contained in:
parent
74eb1135df
commit
894d23bb7c
|
@ -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."
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: [
|
||||||
|
|
|
@ -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!
|
||||||
|
|
|
@ -2289,5 +2289,13 @@ const possibleTypesMap = <String, Set<String>>{
|
||||||
'EnumConfigItem',
|
'EnumConfigItem',
|
||||||
'StringConfigItem',
|
'StringConfigItem',
|
||||||
},
|
},
|
||||||
|
'MonitoringMetricsResult': {
|
||||||
|
'MonitoringMetrics',
|
||||||
|
'MonitoringQueryError',
|
||||||
|
},
|
||||||
|
'MonitoringValuesResult': {
|
||||||
|
'MonitoringValues',
|
||||||
|
'MonitoringQueryError',
|
||||||
|
},
|
||||||
'StorageUsageInterface': {'ServiceStorageUsage'},
|
'StorageUsageInterface': {'ServiceStorageUsage'},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 => [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,14 +76,15 @@ class _ContentWidget extends StatelessWidget {
|
||||||
),
|
),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const Gap(8),
|
if (description != null) const Gap(8),
|
||||||
Text(
|
if (description != null)
|
||||||
description,
|
Text(
|
||||||
textAlign: TextAlign.center,
|
description!,
|
||||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
textAlign: TextAlign.center,
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||||
),
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
),
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue