syncthing: Syncthing status & control - #372
Merged
Merged
Conversation
Port of the v4 syncthing-status plugin (by Pir0c0pter0) to the v5 Luau plugin API. Talks to the Syncthing REST API directly via noctalia.http (no helper scripts). Bar widget, Folders|Devices panel with per-folder and per-device pause/resume and rescan, sync progress, transfer rates, event notifications, /st launcher provider, global-pause shortcut, and a desktop widget. en + it translations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review follow-up on noctalia-dev#71: - every REST request now goes through noctalia.http with allow_insecure_tls (plugin_api 3 -> 7); the curl branch that interpolated the API key, base URL and body into a shell command line is gone, and the GUI opener shell-quotes its URL. - declare gio and xdg-open in dependencies and document all three commands in the README Requirements section. - format the last-check time with strftime, seed bar and desktop widgets from the current snapshot instead of waiting for the next publish, and give the panel room for four device rows without a border on each.
The root column's height is measured inside its padding, so pinning it to the manifest height pushed the footer past the panel edge; subtract the padding instead. publish() was the only place clearing `busy`, so an HTTP callback that never fired left the poller stuck with no further updates and a frozen last-check time. Restart the poll after a grace period.
Four devices needed a scrollbar. The idle summary line repeated what the header and the device tile already say, so it now shows only for the states that add information; the column gap is tighter and the content leaves slack for the panel frame so the footer is not flush against it.
The panel bound each row to one of 16 predeclared callback slots, so both the folder and the device list were silently cut off past the sixteenth entry. Pointer props take a function, so each row's buttons now close over their own folder or device: MAX_FOLDER_SLOTS, the slot table, the *At helpers and the numbered onPause/onResume/onRescan globals are gone. plugin_api rises to 9, the level that introduced function callbacks. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…he poll The poller kept stalling, and the log finally named it: 'update' exceeded its CPU budget, in parseConfigXml. Every poll re-read Syncthing's config.xml — tens of kilobytes — and located the <gui> block with a non-greedy pattern that rescans the tail of the file for each starting position. When the callback was cut off mid-flight, busy stayed set and the poller never recovered. config.xml is now parsed once and cached, by index rather than by pattern. The cache is dropped when the plugin settings change and while nothing is answering, so a Syncthing that gets installed or reconfigured later is still picked up. The watchdog it needed becomes a generation number instead: callbacks from a superseded poll are dropped rather than racing the current one, which the grace-period watchdog could not prevent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A LauncherResult badge is drawn in the icon slot and scaled up to fill it, so "Up to date" covered the whole row and hid the state glyph. The state now reads as the first half of the subtitle, the way the rest of the launcher rows are built. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Plugin
rylos/syncthingplugin.toml)This supersedes #71, which @ItsLemmy closed as stale with "re-open the PR when
cleaned up" — GitHub refuses to reopen it now that the branch has been rebased,
so here it is as a fresh PR. Both review points are addressed, and the bug that
made the poller stall has been found and fixed; details below.
What it does
Syncthing status and control from the bar: overall state at a glance, and a
panel to pause, resume and rescan without opening the web UI.
paused, unreachable, API key rejected …), with an optional count.
Folders with per-row pause/resume and rescan, and Devices with per-row
pause/resume and the address and version of each connected device.
/st) — every folder, with its state, and Enter to rescan.config.xml(
~/.local/state/syncthing/config.xml, falling back to~/.config/syncthing/config.xml), so there is nothing to configure in thecommon case; both can be overridden in settings.
External dependencies
syncthing— the REST API on the local instance is what the plugin polls.gio/xdg-open— only to open the Syncthing web UI when the header buttonis clicked. Both are declared in
dependencies.No
curl: HTTP goes through the host's own request API.Testing
In daily use on niri against a real Syncthing instance with 3 folders and 4
devices, which is what the screenshots show.
over the course of using it.
/stand the rescan rows, re-checked for this PR after the badgefix below.
~/.cache/noctalia/noctalia.logacross a long session,which is how the CPU-budget abort described below was found, and confirmed
clean afterwards.
noctalia plugins lint syncthingandpython3 .github/workflows/scripts/validate-plugins.pyare clean.Since the version that was closed as stale:
curlfallback is gone, andgioandxdg-openare declared.its own folder or device (
plugin_api9), so the list has no length limit —previously it silently stopped at the sixteenth row.
updatewas blowing the host's per-callbackCPU budget inside
parseConfigXml, which re-read and re-scanned the ~16 KBconfig.xmlon every poll with a non-greedy<gui[^>]*>(.-)</gui>pattern.The file is now located by index and parsed once, cached until the config
changes; the aborted-callback watchdog became a generation counter, so a late
reply can no longer overwrite a newer snapshot.
badge. A badge is drawn in the icon slotand scaled to fill it, so "Up to date" covered the whole row; the state is
the first half of the subtitle now.
Screenshots / Videos
Panel, Folders tab:
Panel, Devices tab (device addresses masked):
Launcher,
/st:Bar widget — the circular arrows, next to the Tailnet dots:
Checklist
idafter the/inplugin.tomlexactly.plugin.toml,README.md,thumbnail.webp, andtranslations/en.json.README.mdfollows theREADME template, documents
every entry id and dependency, and includes exact panel IPC commands and launcher prefixes where applicable.
thumbnail.webpwith the thumbnail generator.versionfollows semver and is bumped in this PR;plugin_apiis the oldest API level this plugin requires.understand that language well enough to review and maintain it (no unreviewed machine/LLM translations).
catalog.toml; CI generates it.Code review attestation
Plugins run as trusted, unsandboxed Luau in the user's session. Confirm:
licensedeclared inplugin.toml.