Skip to content

Commit b3df03a

Browse files
feat: integrate LogbookProvider and add LogbookScreen to dashboard for enhanced logging functionality
1 parent 30a92a0 commit b3df03a

5 files changed

Lines changed: 677 additions & 3 deletions

File tree

dashboard/lib/main.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
22
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
33
import 'package:provider/provider.dart';
44
import 'package:sentracore_dashboard/providers/engine_provider.dart';
5+
import 'package:sentracore_dashboard/providers/logbook_provider.dart';
56
import 'package:sentracore_dashboard/providers/settings_provider.dart';
67
import 'package:sentracore_dashboard/navigation/dashboard_navigation.dart';
78
import 'package:sentracore_dashboard/screens/dashboard_screen.dart';
@@ -43,6 +44,9 @@ class SentraCoreApp extends StatelessWidget {
4344
providers: [
4445
Provider<DesktopNotificationService>.value(value: notifications),
4546
ChangeNotifierProvider<SettingsProvider>.value(value: settings),
47+
ChangeNotifierProvider<LogbookProvider>(
48+
create: (_) => LogbookProvider()..load(),
49+
),
4650
ChangeNotifierProvider(
4751
create: (_) => EngineProvider(
4852
settings: settings,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
class LogbookEntry {
2+
final String id;
3+
final DateTime at;
4+
final String processName;
5+
final double cpuPercent;
6+
final double memPercent;
7+
final double diskPressurePercent;
8+
final String notes;
9+
10+
const LogbookEntry({
11+
required this.id,
12+
required this.at,
13+
required this.processName,
14+
required this.cpuPercent,
15+
required this.memPercent,
16+
required this.diskPressurePercent,
17+
required this.notes,
18+
});
19+
20+
Map<String, dynamic> toJson() {
21+
return {
22+
'id': id,
23+
'at': at.toIso8601String(),
24+
'process_name': processName,
25+
'cpu_percent': cpuPercent,
26+
'mem_percent': memPercent,
27+
'disk_pressure_percent': diskPressurePercent,
28+
'notes': notes,
29+
};
30+
}
31+
32+
factory LogbookEntry.fromJson(Map<String, dynamic> json) {
33+
final atRaw = json['at'];
34+
DateTime at;
35+
if (atRaw is String) {
36+
at = DateTime.tryParse(atRaw) ?? DateTime.fromMillisecondsSinceEpoch(0);
37+
} else {
38+
at = DateTime.fromMillisecondsSinceEpoch(0);
39+
}
40+
41+
return LogbookEntry(
42+
id: '${json['id'] ?? ''}'.trim().isEmpty ? 'unknown' : '${json['id']}',
43+
at: at,
44+
processName: '${json['process_name'] ?? ''}'.trim(),
45+
cpuPercent: (json['cpu_percent'] as num?)?.toDouble() ?? 0,
46+
memPercent: (json['mem_percent'] as num?)?.toDouble() ?? 0,
47+
diskPressurePercent:
48+
(json['disk_pressure_percent'] as num?)?.toDouble() ?? 0,
49+
notes: '${json['notes'] ?? ''}',
50+
);
51+
}
52+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import 'dart:async';
2+
import 'dart:convert';
3+
4+
import 'package:flutter/material.dart';
5+
import 'package:shared_preferences/shared_preferences.dart';
6+
7+
import 'package:sentracore_dashboard/models/logbook_entry.dart';
8+
9+
class LogbookProvider extends ChangeNotifier {
10+
static const _kLogbookEntries = 'logbook_entries_v1';
11+
12+
final List<LogbookEntry> _entries = [];
13+
bool _loaded = false;
14+
Timer? _saveDebounce;
15+
16+
List<LogbookEntry> get entries {
17+
final copy = List<LogbookEntry>.from(_entries);
18+
copy.sort((a, b) => b.at.compareTo(a.at));
19+
return copy;
20+
}
21+
22+
bool get loaded => _loaded;
23+
24+
Future<void> load() async {
25+
if (_loaded) return;
26+
final p = await SharedPreferences.getInstance();
27+
final raw = p.getString(_kLogbookEntries);
28+
if (raw == null || raw.trim().isEmpty) {
29+
_loaded = true;
30+
notifyListeners();
31+
return;
32+
}
33+
try {
34+
final decoded = jsonDecode(raw);
35+
if (decoded is List) {
36+
_entries
37+
..clear()
38+
..addAll(
39+
decoded.whereType<Map>().map(
40+
(m) => LogbookEntry.fromJson(
41+
Map<String, dynamic>.from(m),
42+
),
43+
),
44+
);
45+
}
46+
} catch (_) {
47+
// Keep empty logbook on corrupt payload; user can rebuild over time.
48+
}
49+
_loaded = true;
50+
notifyListeners();
51+
}
52+
53+
void add(LogbookEntry e) {
54+
_entries.add(e);
55+
notifyListeners();
56+
_scheduleSave();
57+
}
58+
59+
void deleteById(String id) {
60+
_entries.removeWhere((e) => e.id == id);
61+
notifyListeners();
62+
_scheduleSave();
63+
}
64+
65+
void clear() {
66+
_entries.clear();
67+
notifyListeners();
68+
_scheduleSave();
69+
}
70+
71+
void _scheduleSave() {
72+
_saveDebounce?.cancel();
73+
_saveDebounce = Timer(const Duration(milliseconds: 350), () {
74+
unawaited(_saveNow());
75+
});
76+
}
77+
78+
Future<void> _saveNow() async {
79+
final p = await SharedPreferences.getInstance();
80+
final payload = jsonEncode(_entries.map((e) => e.toJson()).toList());
81+
await p.setString(_kLogbookEntries, payload);
82+
}
83+
84+
@override
85+
void dispose() {
86+
_saveDebounce?.cancel();
87+
_saveDebounce = null;
88+
super.dispose();
89+
}
90+
}

dashboard/lib/screens/dashboard_screen.dart

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:sentracore_dashboard/screens/performance_screen.dart';
77
import 'package:sentracore_dashboard/screens/processes_screen.dart';
88
import 'package:sentracore_dashboard/screens/diagnostics_screen.dart';
99
import 'package:sentracore_dashboard/screens/settings_screen.dart';
10+
import 'package:sentracore_dashboard/screens/logbook_screen.dart';
1011
import 'package:sentracore_dashboard/theme/app_theme.dart';
1112
import 'package:sentracore_dashboard/widgets/connection_banner.dart';
1213
import 'package:sentracore_dashboard/providers/settings_provider.dart';
@@ -28,7 +29,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
2829
super.initState();
2930
DashboardNavigation.selectMainTab = (i) {
3031
if (!mounted) return;
31-
setState(() => _selectedIndex = i.clamp(0, 4));
32+
setState(() => _selectedIndex = i.clamp(0, 5));
3233
};
3334
}
3435

@@ -42,6 +43,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
4243
OverviewScreen(),
4344
PerformanceScreen(),
4445
ProcessesScreen(),
46+
LogbookScreen(),
4547
DiagnosticsScreen(),
4648
SettingsScreen(),
4749
];
@@ -219,9 +221,11 @@ class _SentraNavRail extends StatelessWidget {
219221
Icons.analytics_rounded, 'Performance'),
220222
_navItem(context, 2, Icons.layers_outlined, Icons.layers_rounded,
221223
'Processes'),
222-
_navItem(context, 3, Icons.troubleshoot_outlined,
224+
_navItem(
225+
context, 3, Icons.book_outlined, Icons.book_rounded, 'Logbook'),
226+
_navItem(context, 4, Icons.troubleshoot_outlined,
223227
Icons.troubleshoot_rounded, 'Diagnostics'),
224-
_navItem(context, 4, Icons.settings_outlined, Icons.settings_rounded,
228+
_navItem(context, 5, Icons.settings_outlined, Icons.settings_rounded,
225229
'Settings'),
226230
const Spacer(),
227231
// Theme Toggle

0 commit comments

Comments
 (0)