Skip to content

Commit d9347e1

Browse files
feat: enhance process management and settings functionality with new safeguard features and improved process fetching
1 parent 0c717e4 commit d9347e1

8 files changed

Lines changed: 223 additions & 72 deletions

File tree

dashboard/lib/providers/engine_provider.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ class EngineProvider extends ChangeNotifier {
185185

186186
_connected = true;
187187
notifyListeners();
188+
unawaited(_fetchProcesses());
189+
unawaited(_fetchEvents());
188190
} catch (e) {
189191
_connected = false;
190192
_bootstrapErrorPending = false;
@@ -299,10 +301,15 @@ class EngineProvider extends ChangeNotifier {
299301

300302
Future<void> _fetchProcesses() async {
301303
try {
302-
_processes = await _service.getProcesses();
304+
_processes = await _service.getProcesses(limit: 50);
305+
notifyListeners();
303306
} catch (_) {}
304307
}
305308

309+
Future<void> refreshProcesses() async {
310+
await _fetchProcesses();
311+
}
312+
306313
Future<void> _fetchEvents() async {
307314
try {
308315
_events = await _service.getEvents();

dashboard/lib/providers/settings_provider.dart

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,51 @@ class SettingsProvider extends ChangeNotifier {
118118
notifyListeners();
119119
}
120120

121+
List<String> _parseSafeguardLines() {
122+
return _safeguardProcessNames
123+
.split(RegExp(r'[\r\n,;]+'))
124+
.map((s) => s.trim())
125+
.where((s) => s.isNotEmpty)
126+
.toList();
127+
}
128+
129+
/// Names to show as safeguard checkboxes: snapshot processes plus any saved names.
130+
List<String> safeguardPickList(Iterable<String> snapshotProcessNames) {
131+
final merged = {
132+
...snapshotProcessNames,
133+
..._parseSafeguardLines(),
134+
}.toList();
135+
merged.sort(
136+
(a, b) => a.toLowerCase().compareTo(b.toLowerCase()),
137+
);
138+
return merged;
139+
}
140+
141+
bool safeguardHasName(String name) {
142+
final l = name.toLowerCase();
143+
return _parseSafeguardLines().any((x) => x.toLowerCase() == l);
144+
}
145+
146+
void toggleSafeguardProcessName(String name, bool selected) {
147+
final lines = List<String>.from(_parseSafeguardLines());
148+
final l = name.toLowerCase();
149+
if (selected) {
150+
if (!lines.any((x) => x.toLowerCase() == l)) {
151+
lines.add(name);
152+
}
153+
} else {
154+
lines.removeWhere((x) => x.toLowerCase() == l);
155+
}
156+
_safeguardProcessNames = lines.join('\n');
157+
notifyListeners();
158+
}
159+
160+
void addSafeguardProcessNameLine(String raw) {
161+
final t = raw.trim();
162+
if (t.isEmpty) return;
163+
toggleSafeguardProcessName(t, true);
164+
}
165+
121166
/// Apply JSON from [GET /api/v1/preferences] (does not persist to disk here).
122167
void applyFromEngine(Map<String, dynamic> json) {
123168
_alertCpuPercent = (json['alert_cpu_percent'] as num?)?.toDouble() ?? 85;
@@ -136,11 +181,7 @@ class SettingsProvider extends ChangeNotifier {
136181
}
137182

138183
Map<String, dynamic> toEngineJson() {
139-
final lines = _safeguardProcessNames
140-
.split(RegExp(r'[\r\n,;]+'))
141-
.map((s) => s.trim())
142-
.where((s) => s.isNotEmpty)
143-
.toList();
184+
final lines = _parseSafeguardLines();
144185
return {
145186
'alert_cpu_percent': _alertCpuPercent,
146187
'alert_memory_percent': _alertMemoryPercent,

dashboard/lib/screens/processes_screen.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,12 @@ class _ProcessesScreenState extends State<ProcessesScreen> {
5151
),
5252
),
5353
Text(
54-
'Ranked by sustained system impact',
54+
'Top processes by impact — not every app. Memory % is each '
55+
'process’s share of RAM; it will not add up to overall usage.',
5556
style: TextStyle(
5657
color: AppTheme.textMutedFor(context),
5758
fontSize: 11,
59+
height: 1.25,
5860
),
5961
),
6062
],

dashboard/lib/screens/settings_screen.dart

Lines changed: 122 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,27 @@ class SettingsScreen extends StatefulWidget {
1212
}
1313

1414
class _SettingsScreenState extends State<SettingsScreen> {
15-
late final TextEditingController _safeguardCtrl;
16-
late final SettingsProvider _settingsRef;
17-
18-
void _syncSafeguardFromProvider() {
19-
final t = _settingsRef.safeguardProcessNames;
20-
if (_safeguardCtrl.text != t) {
21-
_safeguardCtrl.value = TextEditingValue(
22-
text: t,
23-
selection: TextSelection.collapsed(offset: t.length),
24-
);
25-
}
26-
}
15+
late final TextEditingController _manualSafeguardCtrl;
2716

2817
@override
2918
void initState() {
3019
super.initState();
31-
_settingsRef = context.read<SettingsProvider>();
32-
_safeguardCtrl = TextEditingController(text: _settingsRef.safeguardProcessNames);
33-
_settingsRef.addListener(_syncSafeguardFromProvider);
20+
_manualSafeguardCtrl = TextEditingController();
3421
}
3522

3623
@override
3724
void dispose() {
38-
_settingsRef.removeListener(_syncSafeguardFromProvider);
39-
_safeguardCtrl.dispose();
25+
_manualSafeguardCtrl.dispose();
4026
super.dispose();
4127
}
4228

4329
@override
4430
Widget build(BuildContext context) {
4531
final settings = context.watch<SettingsProvider>();
32+
final engine = context.watch<EngineProvider>();
33+
final pickNames = settings.safeguardPickList(
34+
engine.processes.map((p) => p.name),
35+
);
4636

4737
return Column(
4838
crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -140,8 +130,8 @@ class _SettingsScreenState extends State<SettingsScreen> {
140130
const SizedBox(height: 8),
141131
Text(
142132
'After an alert fires, the engine may end matching processes '
143-
'(graceful terminate) to reduce load. One name per line; '
144-
'include .exe or omit it (e.g. chrome or chrome.exe). '
133+
'(graceful terminate) to reduce load. Choose names from what '
134+
'the engine currently sees, or add another name manually. '
145135
'Use only for apps you accept losing unsaved work.',
146136
style: TextStyle(
147137
color: AppTheme.textMutedFor(context),
@@ -152,6 +142,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
152142
const SizedBox(height: 12),
153143
Card(
154144
child: Column(
145+
crossAxisAlignment: CrossAxisAlignment.stretch,
155146
children: [
156147
SwitchListTile(
157148
title: Text(
@@ -170,22 +161,120 @@ class _SettingsScreenState extends State<SettingsScreen> {
170161
onChanged: settings.setSafeguardEnabled,
171162
),
172163
Padding(
173-
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
174-
child: TextField(
175-
controller: _safeguardCtrl,
176-
maxLines: 5,
177-
enabled: settings.safeguardEnabled,
178-
decoration: InputDecoration(
179-
labelText: 'Process names',
180-
hintText: 'e.g.\nSomeHeavyApp.exe\nAnotherApp',
181-
alignLabelWithHint: true,
182-
border: const OutlineInputBorder(),
183-
),
164+
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
165+
child: Row(
166+
children: [
167+
Text(
168+
'Safe to close (select processes)',
169+
style: TextStyle(
170+
color: AppTheme.textSecondaryFor(context),
171+
fontSize: 13,
172+
fontWeight: FontWeight.w600,
173+
),
174+
),
175+
const Spacer(),
176+
TextButton.icon(
177+
onPressed: !engine.connected
178+
? null
179+
: () => engine.refreshProcesses(),
180+
icon: const Icon(Icons.refresh, size: 18),
181+
label: const Text('Refresh list'),
182+
),
183+
],
184+
),
185+
),
186+
Padding(
187+
padding: const EdgeInsets.symmetric(horizontal: 16),
188+
child: Text(
189+
!engine.connected
190+
? 'Connect to the engine to load process names from this PC.'
191+
: pickNames.isEmpty
192+
? 'No processes yet — tap Refresh after the engine runs a few seconds.'
193+
: 'Checked names are allowed for safeguard termination.',
184194
style: TextStyle(
185-
color: AppTheme.textPrimaryFor(context),
186-
fontSize: 13,
195+
color: AppTheme.textMutedFor(context),
196+
fontSize: 11,
197+
height: 1.3,
187198
),
188-
onChanged: settings.setSafeguardProcessNames,
199+
),
200+
),
201+
const SizedBox(height: 8),
202+
SizedBox(
203+
height: 200,
204+
child: ListView.builder(
205+
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
206+
itemCount: pickNames.length,
207+
itemBuilder: (context, i) {
208+
final name = pickNames[i];
209+
return CheckboxListTile(
210+
dense: true,
211+
enabled: settings.safeguardEnabled,
212+
value: settings.safeguardHasName(name),
213+
onChanged: settings.safeguardEnabled
214+
? (v) => settings.toggleSafeguardProcessName(
215+
name,
216+
v ?? false,
217+
)
218+
: null,
219+
title: Text(
220+
name,
221+
style: TextStyle(
222+
color: AppTheme.textPrimaryFor(context),
223+
fontSize: 13,
224+
),
225+
maxLines: 1,
226+
overflow: TextOverflow.ellipsis,
227+
),
228+
);
229+
},
230+
),
231+
),
232+
Padding(
233+
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
234+
child: Row(
235+
crossAxisAlignment: CrossAxisAlignment.start,
236+
children: [
237+
Expanded(
238+
child: TextField(
239+
controller: _manualSafeguardCtrl,
240+
enabled: settings.safeguardEnabled,
241+
decoration: InputDecoration(
242+
labelText: 'Add other process name',
243+
hintText: 'e.g. MyApp.exe',
244+
border: const OutlineInputBorder(),
245+
isDense: true,
246+
),
247+
style: TextStyle(
248+
color: AppTheme.textPrimaryFor(context),
249+
fontSize: 13,
250+
),
251+
onSubmitted: settings.safeguardEnabled
252+
? (_) {
253+
settings.addSafeguardProcessNameLine(
254+
_manualSafeguardCtrl.text,
255+
);
256+
_manualSafeguardCtrl.clear();
257+
}
258+
: null,
259+
),
260+
),
261+
const SizedBox(width: 8),
262+
Padding(
263+
padding: const EdgeInsets.only(top: 8),
264+
child: IconButton.filledTonal(
265+
tooltip: 'Add name',
266+
onPressed: !settings.safeguardEnabled
267+
? null
268+
: () {
269+
settings.addSafeguardProcessNameLine(
270+
_manualSafeguardCtrl.text,
271+
);
272+
_manualSafeguardCtrl.clear();
273+
},
274+
icon: const Icon(Icons.add, size: 22),
275+
),
276+
),
277+
],
189278
),
190279
),
191280
],
@@ -261,7 +350,6 @@ class _SettingsScreenState extends State<SettingsScreen> {
261350
const SizedBox(height: 24),
262351
FilledButton.icon(
263352
onPressed: () async {
264-
settings.setSafeguardProcessNames(_safeguardCtrl.text);
265353
await settings.save();
266354
if (!context.mounted) return;
267355
final eng = context.read<EngineProvider>();

dashboard/lib/services/engine_service.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class EngineService {
4040
return null;
4141
}
4242

43-
Future<List<ProcessImpact>> getProcesses() async {
44-
final data = await _get('/api/v1/processes');
43+
Future<List<ProcessImpact>> getProcesses({int limit = 50}) async {
44+
final data = await _get('/api/v1/processes?limit=$limit');
4545
if (data != null && data['processes'] != null) {
4646
return (data['processes'] as List)
4747
.map((p) => ProcessImpact.fromJson(p))

dashboard/lib/widgets/connection_banner.dart

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,31 +16,40 @@ class ConnectionBanner extends StatelessWidget {
1616
bottom: BorderSide(color: AppTheme.warning.withValues(alpha: 0.3)),
1717
),
1818
),
19-
child: Row(
19+
child: Column(
20+
crossAxisAlignment: CrossAxisAlignment.start,
2021
children: [
21-
SizedBox(
22-
width: 14,
23-
height: 14,
24-
child: CircularProgressIndicator(
25-
strokeWidth: 2,
26-
valueColor: AlwaysStoppedAnimation<Color>(AppTheme.warning),
27-
),
28-
),
29-
const SizedBox(width: 10),
30-
Text(
31-
'Connecting to SentraCore engine at 127.0.0.1:8740...',
32-
style: TextStyle(
33-
color: AppTheme.warning,
34-
fontSize: 12,
35-
fontWeight: FontWeight.w500,
36-
),
22+
Row(
23+
children: [
24+
SizedBox(
25+
width: 14,
26+
height: 14,
27+
child: CircularProgressIndicator(
28+
strokeWidth: 2,
29+
valueColor: AlwaysStoppedAnimation<Color>(AppTheme.warning),
30+
),
31+
),
32+
const SizedBox(width: 10),
33+
Expanded(
34+
child: Text(
35+
'Connecting to SentraCore engine…',
36+
style: TextStyle(
37+
color: AppTheme.warning,
38+
fontSize: 12,
39+
fontWeight: FontWeight.w500,
40+
),
41+
),
42+
),
43+
],
3744
),
38-
const Spacer(),
39-
Text(
40-
'Make sure the engine is running: python -m engine.main',
41-
style: TextStyle(
42-
color: AppTheme.textMutedFor(context),
43-
fontSize: 11,
45+
Padding(
46+
padding: const EdgeInsets.only(left: 24, top: 6),
47+
child: Text(
48+
'Waiting for the local engine. This may take a few seconds.',
49+
style: TextStyle(
50+
color: AppTheme.textMutedFor(context),
51+
fontSize: 11,
52+
),
4453
),
4554
),
4655
],

0 commit comments

Comments
 (0)