feat(nix-monitor): add nix-monitor plugin

This commit is contained in:
avivbintangaringga
2026-07-14 11:32:40 +07:00
parent ebbfa9c845
commit 7133dfb04a
7 changed files with 569 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# Nix Monitor
![thumbnail](thumbnail.webp)
*Nix Monitor* checks Nixpkgs update by comparing local nix hash and remote nixpkgs's hash. It also can run update and clean command.
No AI is used here but this is my first time writing Luau and plugin in general, so expect some bug. PRs are welcomed!
+55
View File
@@ -0,0 +1,55 @@
function tr(key)
return noctalia.tr(key)
end
function barCfg(key)
return barWidget.getConfig(key)
end
function getState(key)
return noctalia.state.get(key)
end
function displayWidget(glyph: string, text: string, color: string?)
if color == nil then
color = "on_surface"
end
if barCfg("colorize_text") then
barWidget.setColor(color)
end
if barCfg("colorize_glyph") then
barWidget.setGlyphColor(color)
end
if barCfg("show_glyph") then
barWidget.setGlyph(glyph)
end
if barCfg("show_text") then
barWidget.setText(text)
end
end
function update()
if getState("isRunning") then
displayWidget(barCfg("checking_glyph"), tr("bar.checking"), barCfg("checking_color"))
else
if getState("isUpdateAvailable") then
displayWidget(barCfg("update_available_glyph"), tr("bar.update_available"), barCfg("update_available_color"))
else
if getState("localHash") == "n/a" or getState("remoteHash") == "n/a" then
displayWidget(barCfg("unknown_glyph"), tr("bar.unknown"), barCfg("unknown_color"))
else
displayWidget(barCfg("up_to_date_glyph"), tr("bar.up_to_date"), barCfg("up_to_date_color"))
end
end
end
end
function onClick()
noctalia.togglePanel("avivbintangaringga/nix-monitor:panel")
end
+111
View File
@@ -0,0 +1,111 @@
noctalia.setUpdateInterval(100)
function tr(key)
return noctalia.tr(key)
end
function cfg(key)
return noctalia.getConfig(key)
end
function getState(key)
return noctalia.state.get(key)
end
function setState(key, val)
noctalia.state.set(key, val)
end
function watchState(key, cb)
noctalia.state.watch(key, cb)
end
local branch = cfg("branch")
local updateCheckInterval = cfg("check_interval")
local localHashCmd = "nixos-version --hash 2> /dev/null | cut -c -7"
local remoteHashCmd = "git ls-remote https://github.com/NixOS/nixpkgs " .. branch .. " 2>/dev/null | cut -c 1-7"
local completedCommands = 0
setState("lastCheckTime", 0)
setState("lastCheckTimeStr", "n/a")
setState("localHash", "n/a")
setState("remoteHash", "n/a")
setState("isRunning", false)
setState("isUpdateAvailable", false)
function update()
if (os.time() - getState("lastCheckTime")) >= 60 * updateCheckInterval then
checkUpdate()
end
end
function checkUpdate()
if getState("isRunning") then
return
end
setState("lastCheckTime", os.time())
noctalia.runAsync("date", function(date) setState("lastCheckTimeStr", date.stdout) end)
if cfg("show_checking_notification") then
noctalia.notify(tr("notification.checking_update"))
end
setState("isRunning", true)
completedCommands = 0
noctalia.runAsync(localHashCmd, getLocalHash)
noctalia.runStream(remoteHashCmd, getRemoteHash)
end
function getLocalHash(result)
if getState("isRunning") ~= true then
return
end
setState("localHash", result.stdout)
completedCommands += 1
onHashCheckCompleted()
end
function getRemoteHash(line)
if getState("isRunning") ~= true then
return
end
setState("remoteHash", line)
completedCommands += 1
onHashCheckCompleted()
end
function onHashCheckCompleted()
if completedCommands < 2 then
return
end
if getState("localHash") ~= getState("remoteHash") then
setState("isUpdateAvailable", true)
if cfg("show_update_notification") then
noctalia.notify(tr("notification.update_is_available"))
end
end
if cfg("show_checking_notification") then
noctalia.notify(tr("notification.update_check_completed"))
end
setState("isRunning", false)
end
watchState("command", function(command)
if command == "check" then
checkUpdate()
end
if command == "cancel" then
noctalia.runAsync(`kill $(ps -ef | grep "git ls-remote https://github.com/NixOS/nixpkgs" | grep -v grep | awk '\{print $2\}')`)
setState("isRunning", false)
completedCommands = 0
end
end)
+186
View File
@@ -0,0 +1,186 @@
function tr(key)
return noctalia.tr(key)
end
function barCfg(key)
return barWidget.getConfig(key)
end
function cfg(key)
return noctalia.getConfig(key)
end
function getState(key)
return noctalia.state.get(key)
end
function setState(key, val)
noctalia.state.set(key, val)
end
function watchState(key, cb)
noctalia.state.watch(key, cb)
end
local function labelBox(subtext, text, glyph)
return ui.column({
fill = "surface/0.7",
flexGrow = 1,
height = 100,
radius = 8,
align = "center",
padding = 12,
justify = "space_between"
}, {
ui.glyph({
name = glyph,
size = 30
}),
ui.label({
text = text,
fontWeight = "bold"
}),
ui.label({
text = subtext,
fontSize = 12,
fontWeight = "medium"
}),
})
end
local function render()
local firstBtn = btnCheck
if getState("isRunning") then
firstBtn = ui.button({
text = tr("panel.cancel"),
glyph = "cancel",
flexGrow = 1,
variant = "destructive",
onClick = "onCancelClicked",
})
else
if getState("isUpdateAvailable") then
firstBtn = ui.button({
text = tr("panel.update"),
glyph = "progress-down",
flexGrow = 1,
variant = "primary",
onClick = "onUpdateClicked",
})
else
firstBtn = ui.button({
text = tr("panel.check"),
glyph = "search",
flexGrow = 1,
variant = "outline",
onClick = "onCheckClicked",
})
end
end
local lastCheckTimeStr = getState("lastCheckTimeStr")
panel.render(
ui.column({
gap = 8
}, {
ui.row({
justify = "space_between",
align = "center"
}, {
ui.label({
text = tr("panel.title"),
fontSize = 20,
}),
ui.label({
text = cfg("branch"),
fontSize = 14,
}),
}),
ui.separator({
thickness = 2,
orientation = "horizontal",
spacing = 4
}),
ui.row({
gap = 8,
align = "center",
justify = "space_between"
}, {
labelBox(tr("panel.local"), getState("localHash"), "devices-pc"),
labelBox(tr("panel.remote"), getState("remoteHash"), "world"),
}),
ui.separator({
thickness = 2,
orientation = "horizontal",
spacing = 4
}),
ui.label({
text = `{tr("panel.last_checked")}: {lastCheckTimeStr}`
}),
ui.row({
gap = 8,
align = "center",
justify = "space_between"
}, {
firstBtn,
ui.button({
text = tr("panel.clean"),
glyph = "trash",
flexGrow = 1,
variant = "destructive",
onClick = "onCleanClicked",
}),
})
})
)
end
watchState("isRunning", render)
watchState("lastCheckTimeStr", render)
watchState("localHash", render)
watchState("remoteHash", render)
function onOpen(_ctx)
render()
end
function onCheckClicked()
setState("command", "check")
end
function onCancelClicked()
setState("command", "cancel")
end
function onUpdateClicked()
command = cfg("update_command")
if command == "" then
noctalia.notifyError(tr("notification.update_command_is_empty"))
return
end
if cfg("close_on_enter") then
command = `{command} && echo && read -p "{tr("terminal.press_enter_to_exit")}"`
end
panel.close()
noctalia.runInTerminal(command)
end
function onCleanClicked()
command = cfg("clean_command")
if command == "" then
noctalia.notifyError(tr("notification.clean_command_is_empty"))
return
end
if cfg("close_on_enter") then
command = `{command} && echo && read -p "{tr("terminal.press_enter_to_exit")}"`
end
panel.close()
noctalia.runInTerminal(command)
end
+164
View File
@@ -0,0 +1,164 @@
id = "avivbintangaringga/nix-monitor"
name = "Nix Monitor"
version = "1.0.0"
min_noctalia = "5.0.0"
author = "avivbintangaringga"
description = "Nixpkgs update monitor"
tags = [ "nix", "utility" ]
dependencies = [ "nixos-version", "git", "pkill" ]
[[widget]]
id = "Nix Monitor"
entry = "nix_monitor.luau"
[[widget.setting]]
key = "show_text"
type = "boolean"
label_key = "setting.widget.show_text.label"
default = true
[[widget.setting]]
key = "colorize_text"
type = "boolean"
label_key = "setting.widget.colorize_text.label"
default = false
[[widget.setting]]
key = "show_glyph"
type = "boolean"
label_key = "setting.widget.show_glyph.label"
default = true
[[widget.setting]]
key = "colorize_glyph"
type = "boolean"
label_key = "setting.widget.colorize_glyph.label"
default = true
[[widget.setting]]
key = "up_to_date_glyph"
type = "glyph"
label_key = "setting.widget.up_to_date_glyph.label"
default = "rosette-discount-check"
[[widget.setting]]
key = "up_to_date_color"
type = "color"
label_key = "setting.widget.up_to_date_color.label"
default = "#57ff57"
[[widget.setting]]
key = "checking_glyph"
type = "glyph"
label_key = "setting.widget.checking_glyph.label"
default = "loader-3"
[[widget.setting]]
key = "checking_color"
type = "color"
label_key = "setting.widget.checking_color.label"
default = "#ffeb57"
[[widget.setting]]
key = "update_available_glyph"
type = "glyph"
label_key = "setting.widget.update_available_glyph.label"
default = "cloud-download"
[[widget.setting]]
key = "update_available_color"
type = "color"
label_key = "setting.widget.update_available_color.label"
default = "#ff5757"
[[widget.setting]]
key = "unknown_glyph"
type = "glyph"
label_key = "setting.widget.unknown_glyph.label"
default = "cloud-question"
[[widget.setting]]
key = "unknown_color"
type = "color"
label_key = "setting.widget.unknown_color.label"
default = "on_surface"
[[setting]]
key = "check_interval"
type = "int"
label_key = "setting.check_interval.label"
description_key = "setting.check_interval.description"
default = 60
min = 10
[[setting]]
key = "show_checking_notification"
type = "boolean"
label_key = "setting.checking_notification.label"
description_key = "setting.checking_notification.description"
default = false
[[setting]]
key = "show_update_notification"
type = "boolean"
label_key = "setting.show_update_notification.label"
description_key = "setting.show_update_notification.description"
default = true
[[setting]]
key = "branch"
type = "select"
label_key = "setting.branch.label"
description_key = "setting.branch.description"
options = [
{ value = "master", label = "Master" },
{ value = "nixos-unstable", label = "NixOS Unstable" },
{ value = "nixos-unstable-small", label = "NixOS Unstable Small" },
{ value = "nixos-25.11", label = "NixOS 25.11" },
{ value = "nixos-25.05", label = "NixOS 25.05" },
{ value = "nixos-24.11", label = "NixOS 24.11" },
{ value = "nixos-24.05", label = "NixOS 24.05" },
{ value = "nixos-23.11", label = "NixOS 23.11" },
{ value = "nixos-23.05", label = "NixOS 23.05" },
{ value = "nixos-25.11-small", label = "NixOS 25.11 Small" },
{ value = "nixos-25.05-small", label = "NixOS 25.05 Small" },
{ value = "nixos-24.11-small", label = "NixOS 24.11 Small" },
{ value = "nixos-24.05-small", label = "NixOS 24.05 Small" },
{ value = "nixos-23.11-small", label = "NixOS 23.11 Small" },
{ value = "nixos-23.05-small", label = "NixOS 23.05 Small" },
]
default = "nixos-unstable"
[[setting]]
key = "update_command"
type = "string"
label_key = "setting.update_command.label"
description_key = "setting.update_command.description"
default = ""
[[setting]]
key = "clean_command"
type = "string"
label_key = "setting.clean_command.label"
description_key = "setting.clean_command.description"
default = "nh clean all"
[[setting]]
key = "close_on_enter"
type = "boolean"
label_key = "setting.close_on_enter.label"
description_key = "setting.close_on_enter.description"
default = true
[[panel]]
id = "panel"
entry = "panel.luau"
placement = "attached"
position = "auto"
open_near_click = true
width = 420
height = 290
[[service]]
id = "service"
entry = "nix_monitor_service.luau"
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

+46
View File
@@ -0,0 +1,46 @@
{
"bar.checking": "Checking...",
"bar.update_available": "Update available!",
"bar.up_to_date": "Up to date",
"bar.unknown": "Unknown",
"notification.checking_update": "Checking update...",
"notification.update_is_available": "Update is available!",
"notification.update_check_completed": "Update check completed!",
"notification.update_command_is_empty": "Update command is empty!",
"notification.clean_command_is_empty": "Clean command is empty!",
"panel.title": "Nix Monitor",
"panel.cancel": "Cancel",
"panel.update": "Update",
"panel.check": "Check",
"panel.clean": "Clean",
"panel.local": "Local",
"panel.remote": "Remote",
"panel.last_checked": "Last checked",
"terminal.press_enter_to_exit": "Press enter to exit...",
"setting.widget.show_text.label": "Show text",
"setting.widget.colorize_text.label": "Colorize text",
"setting.widget.show_glyph.label": "Show glyph",
"setting.widget.colorize_glyph.label": "Colorize glyph",
"setting.widget.up_to_date_glyph.label": "Up to date glyph",
"setting.widget.up_to_date_color.label": "Up to date color",
"setting.widget.checking_glyph.label": "Checking glyph",
"setting.widget.checking_color.label": "Checking color",
"setting.widget.update_available_glyph.label": "Update available glyph",
"setting.widget.update_available_color.label": "Update available color",
"setting.widget.unknown_glyph.label": "Unknown status glyph",
"setting.widget.unknown_color.label": "Unknown status color",
"setting.check_interval.label": "Update check interval",
"setting.check_interval.description": "How often to check for updates (in minutes)",
"setting.checking_notification.label": "Show checking update notification",
"setting.checking_notification.description": "Wether to show a notification whenever checking is in progress",
"setting.show_update_notification.label": "Show update notification",
"setting.show_update_notification.description": "Wether to show a notification whenever update is available",
"setting.branch.label": "Branch",
"setting.branch.description": "Which nixpkgs branch to use",
"setting.update_command.label": "Update command",
"setting.update_command.description": "Command to run when clicking Update button",
"setting.clean_command.label": "Clean command",
"setting.clean_command.description": "Command to run when clicking Clean button",
"setting.close_on_enter.label": "Close on press Enter",
"setting.close_on_enter.description": "Add 'Press enter to exit' on the terminal window"
}