- Changed window size to mobile phone format (400x800) - Removed width condition for ActiveProxyFooter - now always visible - Added run-umbrix.sh launch script with icon copying - Stats cards now display on all screen sizes
154 lines
4.2 KiB
Dart
154 lines
4.2 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:umbrix/features/log/data/log_data_providers.dart';
|
|
import 'package:umbrix/features/log/model/log_entity.dart';
|
|
import 'package:umbrix/features/log/model/log_level.dart';
|
|
import 'package:umbrix/features/log/overview/logs_overview_state.dart';
|
|
import 'package:umbrix/utils/riverpod_utils.dart';
|
|
import 'package:umbrix/utils/utils.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import 'package:rxdart/rxdart.dart';
|
|
|
|
part 'logs_overview_notifier.g.dart';
|
|
|
|
@riverpod
|
|
class LogsOverviewNotifier extends _$LogsOverviewNotifier with AppLogger {
|
|
@override
|
|
LogsOverviewState build() {
|
|
ref.disposeDelay(const Duration(seconds: 20));
|
|
state = const LogsOverviewState();
|
|
ref.onDispose(
|
|
() {
|
|
loggy.debug("disposing");
|
|
_listener?.cancel();
|
|
_listener = null;
|
|
},
|
|
);
|
|
ref.onCancel(
|
|
() {
|
|
if (_listener?.isPaused != true) {
|
|
loggy.debug("pausing");
|
|
_listener?.pause();
|
|
}
|
|
},
|
|
);
|
|
ref.onResume(
|
|
() {
|
|
if (!state.paused && (_listener?.isPaused ?? false)) {
|
|
loggy.debug("resuming");
|
|
_listener?.resume();
|
|
}
|
|
},
|
|
);
|
|
|
|
_addListeners();
|
|
return const LogsOverviewState();
|
|
}
|
|
|
|
StreamSubscription? _listener;
|
|
|
|
Future<void> _addListeners() async {
|
|
loggy.debug("adding listeners");
|
|
await _listener?.cancel();
|
|
_listener = ref
|
|
.read(logRepositoryProvider)
|
|
.requireValue
|
|
.watchLogs()
|
|
.throttle(
|
|
(_) => Stream.value(_listener?.isPaused ?? false),
|
|
leading: false,
|
|
trailing: true,
|
|
)
|
|
.throttleTime(
|
|
const Duration(milliseconds: 250),
|
|
leading: false,
|
|
trailing: true,
|
|
)
|
|
.asyncMap(
|
|
(event) async {
|
|
await event.fold(
|
|
(f) {
|
|
_logs = [];
|
|
state = state.copyWith(logs: AsyncError(f, StackTrace.current));
|
|
},
|
|
(a) async {
|
|
_logs = a.reversed;
|
|
state = state.copyWith(logs: AsyncData(await _computeLogs()));
|
|
},
|
|
);
|
|
},
|
|
).listen((event) {});
|
|
}
|
|
|
|
Iterable<LogEntity> _logs = [];
|
|
final _debouncer = CallbackDebouncer(const Duration(milliseconds: 200));
|
|
LogLevel? _levelFilter;
|
|
String _filter = "";
|
|
|
|
// Ограничения для защиты памяти
|
|
static const int _maxLogsInMemory = 1000; // Максимум 1000 записей в памяти
|
|
static const int _trimTo = 500; // Обрезать до 500 при превышении
|
|
|
|
Future<List<LogEntity>> _computeLogs() async {
|
|
// Защита от переполнения памяти
|
|
if (_logs.length > _maxLogsInMemory) {
|
|
loggy.info('Trimming logs: ${_logs.length} → $_trimTo (memory protection)');
|
|
_logs = _logs.take(_trimTo);
|
|
}
|
|
|
|
if (_levelFilter == null && _filter.isEmpty) return _logs.toList();
|
|
return _logs.where((e) {
|
|
return (_filter.isEmpty || e.message.contains(_filter)) && (_levelFilter == null || e.level == null || e.level!.index >= _levelFilter!.index);
|
|
}).toList();
|
|
}
|
|
|
|
void pause() {
|
|
loggy.debug("pausing");
|
|
_listener?.pause();
|
|
state = state.copyWith(paused: true);
|
|
}
|
|
|
|
void resume() {
|
|
loggy.debug("resuming");
|
|
_listener?.resume();
|
|
state = state.copyWith(paused: false);
|
|
}
|
|
|
|
Future<void> clear() async {
|
|
loggy.debug("clearing");
|
|
await ref.read(logRepositoryProvider).requireValue.clearLogs().match(
|
|
(l) {
|
|
loggy.warning("error clearing logs", l);
|
|
},
|
|
(_) {
|
|
_logs = [];
|
|
state = state.copyWith(logs: const AsyncData([]));
|
|
},
|
|
).run();
|
|
}
|
|
|
|
void filterMessage(String? filter) {
|
|
_filter = filter ?? '';
|
|
_debouncer(
|
|
() async {
|
|
if (state.logs case AsyncData()) {
|
|
state = state.copyWith(
|
|
filter: _filter,
|
|
logs: AsyncData(await _computeLogs()),
|
|
);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> filterLevel(LogLevel? level) async {
|
|
_levelFilter = level;
|
|
if (state.logs case AsyncData()) {
|
|
state = state.copyWith(
|
|
levelFilter: _levelFilter,
|
|
logs: AsyncData(await _computeLogs()),
|
|
);
|
|
}
|
|
}
|
|
}
|