Skip to content

fix: use systemctl to list units - #1238

Merged
lollipopkit merged 6 commits into
lollipopkit:mainfrom
Sandarr95:feature/use-systemctl-to-list-units
Jul 21, 2026
Merged

fix: use systemctl to list units#1238
lollipopkit merged 6 commits into
lollipopkit:mainfrom
Sandarr95:feature/use-systemctl-to-list-units

Conversation

@Sandarr95

@Sandarr95 Sandarr95 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

On NixOS the systemd tab is empty, as listing unit files is done through find -type f ..., but on NixOS these are symlinks to the nix store. Using systemctl avoids any discrepancies and ensures future systemd changes are followed. A specific error for missing user units or no systemd connection is also added.

Summary by CodeRabbit

  • New Features

    • Enhanced system and user unit discovery.
    • Added robust parsing for systemctl list-units output, including unit type, scope, state, and multi-word descriptions.
    • Introduced refresh status reporting for system vs user loading failures.
  • Bug Fixes

    • Improved resilience to incomplete, unsupported, or malformed unit output.
    • Refresh behavior is now more consistent, with proper completion handling and user notifications.
  • Tests

    • Added unit parsing tests covering valid output, empty/bus-error cases, and invalid column structures.

Replace scanning of unit-file directories with `systemctl list-units`,
so units are reported regardless of where their files live. Parsing now
lives in `SystemdUnit.parseListUnits`, and the sort comparator is a
top-level `_compareUnits` function.
`getUnits` now returns a `SystemdRefreshResult` so the page can toast when
system units fail to load or when the user manager is unavailable, instead
of silently showing an incomplete list. The initial load moves to the view
so first-load failures are surfaced too.
Covers normal parsing, multi-word descriptions, scope tagging, skipped
unit types/states, empty/error output, and lines with too few columns.
Detect system-scope listing failures the same way as user scope
(non-empty output that parses to no units), so hosts without a working
systemctl get an error toast rather than a silently empty page. The
shared heuristic is extracted into `_listFailed`.
@GT-610

GT-610 commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 599721ac-c949-4c70-a814-76bd78a054a1

📥 Commits

Reviewing files that changed from the base of the PR and between 1bc03d7 and 272993a.

📒 Files selected for processing (2)
  • lib/data/provider/systemd.dart
  • lib/view/page/systemd.dart
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/view/page/systemd.dart
  • lib/data/provider/systemd.dart

📝 Walkthrough

Walkthrough

Adds scoped systemctl list-units command generation and parsing into SystemdUnit objects. Updates SystemdNotifier to refresh system and user units independently, return categorized results, merge and sort units, and manage busy state. SystemdPage now initiates refreshes and handles result snackbars. Adds parser tests for valid output, invalid lines, unsupported mappings, scopes, descriptions, and empty or error output.

Suggested reviewers: gt-610

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: switching systemd unit discovery to systemctl listing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai
coderabbitai Bot requested a review from GT-610 July 21, 2026 02:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
lib/view/page/systemd.dart (1)

36-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move _refresh into an Actions extension per repo convention.

The new _refresh method is defined directly on the State class. As per coding guidelines, lib/view/**/*.dart files should "Split UI into Widget build, Actions, Utils using extension on to achieve this pattern." Consider moving _refresh into a dedicated extension _SystemdPageActions on _SystemdPageState { ... } block for consistency with the rest of the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/view/page/systemd.dart` around lines 36 - 47, Move the _refresh method
out of _SystemdPageState and into a dedicated extension named
_SystemdPageActions on _SystemdPageState, preserving its existing refresh,
mounted-check, and result-handling behavior.

Source: Path instructions

lib/data/provider/systemd.dart (1)

65-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting a shared per-scope fetch helper.

System-scope fetch/parse (Lines 74-80) is handled inline in the outer try, while user-scope fetch/parse (Lines 84-92) has its own nested try/catch with a distinct log label. The outer catch's dprint('Parse systemd', ...) (Line 100) is also reachable by system-side execForOutput failures, not just parse failures, which is a bit misleading for debugging. A small shared helper (e.g. returning (List<SystemdUnit>, bool failed) per scope) would unify error handling/logging for both scopes and remove the asymmetry.

♻️ Proposed refactor
+  Future<(List<SystemdUnit>, bool)> _fetchScope(
+    SshClient client,
+    SystemdUnitScope scope,
+  ) async {
+    try {
+      final raw = await client.execForOutput(scope.listUnitsCmd);
+      final units = SystemdUnit.parseListUnits(raw, scope);
+      return (units, _listFailed(raw, units));
+    } catch (e, s) {
+      dprint('Systemd ${scope.name} units', e, s);
+      return (<SystemdUnit>[], true);
+    }
+  }
+
   Future<SystemdRefreshResult> getUnits() async {
     state = state.copyWith(isBusy: true);

     try {
       final client = _si.client;
       if (client == null) return SystemdRefreshResult.systemFailed;

-      final systemRaw =
-          await client.execForOutput(SystemdUnitScope.system.listUnitsCmd);
-      final systemUnits =
-          SystemdUnit.parseListUnits(systemRaw, SystemdUnitScope.system);
-      if (_listFailed(systemRaw, systemUnits)) {
-        return SystemdRefreshResult.systemFailed;
-      }
-
-      var userUnits = <SystemdUnit>[];
-      var userFailed = false;
-      try {
-        final userRaw =
-            await client.execForOutput(SystemdUnitScope.user.listUnitsCmd);
-        userUnits = SystemdUnit.parseListUnits(userRaw, SystemdUnitScope.user);
-        userFailed = _listFailed(userRaw, userUnits);
-      } catch (e, s) {
-        dprint('Systemd user units', e, s);
-        userFailed = true;
-      }
+      final (systemUnits, systemFailed) =
+          await _fetchScope(client, SystemdUnitScope.system);
+      if (systemFailed) return SystemdRefreshResult.systemFailed;
+
+      final (userUnits, userFailed) =
+          await _fetchScope(client, SystemdUnitScope.user);

       final units = [...userUnits, ...systemUnits]..sort(_compareUnits);
       state = state.copyWith(units: units);
       return userFailed
           ? SystemdRefreshResult.userFailed
           : SystemdRefreshResult.ok;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/data/provider/systemd.dart` around lines 65 - 106, Extract the per-scope
fetch, parse, failure detection, and error logging from getUnits into a shared
helper that accepts a SystemdUnitScope and returns the parsed units plus a
failure flag. Use the helper for both system and user scopes, preserving system
failure as an immediate result while allowing user failure to return partial
results, and replace the misleading outer parse-specific catch with
scope-appropriate logging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/data/provider/systemd.dart`:
- Around line 65-106: Extract the per-scope fetch, parse, failure detection, and
error logging from getUnits into a shared helper that accepts a SystemdUnitScope
and returns the parsed units plus a failure flag. Use the helper for both system
and user scopes, preserving system failure as an immediate result while allowing
user failure to return partial results, and replace the misleading outer
parse-specific catch with scope-appropriate logging.

In `@lib/view/page/systemd.dart`:
- Around line 36-47: Move the _refresh method out of _SystemdPageState and into
a dedicated extension named _SystemdPageActions on _SystemdPageState, preserving
its existing refresh, mounted-check, and result-handling behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: becfb148-8ac8-429e-b526-b19d29e7c875

📥 Commits

Reviewing files that changed from the base of the PR and between 70e3c44 and 1bc03d7.

📒 Files selected for processing (4)
  • lib/data/model/server/systemd.dart
  • lib/data/provider/systemd.dart
  • lib/view/page/systemd.dart
  • test/systemd_test.dart

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 21, 2026
@lollipopkit
lollipopkit merged commit bd35197 into lollipopkit:main Jul 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants