Compare commits
2 Commits
653188201c
...
6224120f7d
| Author | SHA1 | Date | |
|---|---|---|---|
| 6224120f7d | |||
| e4fdd0199d |
@@ -2,9 +2,9 @@
|
||||
name: conky_widget
|
||||
lang: lua
|
||||
domain: infra
|
||||
version: 0.1.0
|
||||
description: "Widget de escritorio Conky con pestañas clickeables (Red / Sistema / Docker) y botones que lanzan herramientas de análisis de red."
|
||||
tags: [conky, widget, desktop, monitoring, network, lua, launcher-buttons]
|
||||
version: 0.2.0
|
||||
description: "Widget de escritorio Conky con 5 pestañas clickeables (Sistema / Red / Docker / Procesos / Devices), reloj permanente, gráficos de línea pixel-perfect y botones que lanzan herramientas de análisis de red."
|
||||
tags: [conky, widget, desktop, monitoring, network, processes, devices, lua, launcher-buttons]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
framework: "conky"
|
||||
@@ -31,13 +31,19 @@ el estado del PC en vivo en la esquina superior derecha del monitor primario.
|
||||
El contenido está organizado en tres pestañas clickeables y, en la pestaña de
|
||||
red, incluye botones que abren herramientas de análisis de paquetes.
|
||||
|
||||
Una franja superior con **reloj + fecha** (formato europeo) es común a todas las
|
||||
pestañas. Los gráficos de línea se dibujan sin antialiasing y con las coordenadas
|
||||
ancladas al pixel (`pixel-perfect`).
|
||||
|
||||
## Pestañas
|
||||
|
||||
| Pestaña | Contenido |
|
||||
|---|---|
|
||||
| **Red** | Velocidad de bajada/subida de `enp5s0`, gráfico histórico en vivo (60 s), conexiones TCP/UDP establecidas, total descargado/subido, y botones [Wireshark] [ntopng] [nethogs]. |
|
||||
| **Sistema** | CPU total + barras por core, RAM, Swap, uso de disco `/`, temperatura, load average y uptime. |
|
||||
| **Docker** | Número de contenedores en marcha / totales y lista de nombres activos (lectura sin sudo). |
|
||||
| **Sistema** | Gráficos de área en vivo: CPU, RAM, CPU temp, GPU, GPU temp, VRAM, disco I/O y red, más barras de uso de los cuatro discos. Las series se vuelven rojas al superar su umbral. |
|
||||
| **Red** | Velocidad de bajada/subida de `enp5s0`, gráfico histórico en vivo, paquetes/s por protocolo, top hosts remotos, conexiones establecidas, total descargado/subido, y botones [Wireshark] [ntopng] [nethogs]. |
|
||||
| **Docker** | Contador `running/total` y, por contenedor, nombre + imagen + estado abreviado (`Up 33h`, `Up 2d`), coloreado según salud. |
|
||||
| **Procesos** | Nº de procesos (total + en ejecución), hilos, carga 1/5/15 min, y tablas TOP CPU / TOP RAM / TOP I/O. Cada fila muestra el PID (clicable: abre `htop -p <pid>`) y el nombre. |
|
||||
| **Devices** | Dispositivos de almacenamiento (`lsblk`, sin loops), interfaces de red físicas con su IP, y dispositivos USB (`lsusb`). |
|
||||
|
||||
## Cómo funciona
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ conky.config = {
|
||||
gap_y = 50, -- separa del panel superior de XFCE
|
||||
minimum_width = 290,
|
||||
maximum_width = 290,
|
||||
minimum_height = 545,
|
||||
minimum_height = 575,
|
||||
|
||||
-- Ventana ('dock' = recibe clicks Y el WM no lo mueve con Alt+drag; 'normal' lo dejaba mover; 'desktop' no recibe clicks) ----------
|
||||
own_window = true,
|
||||
|
||||
+162
-33
@@ -23,11 +23,14 @@ pcall(require, 'cairo_xlib') -- conky >= 1.12 separa el modulo cairo_xlib
|
||||
|
||||
-- Geometria compartida entre dibujo y eventos de raton -----------------------
|
||||
local W = 290
|
||||
local H = 545
|
||||
local TAB_TOP = 4
|
||||
local H = 575
|
||||
local HDR_TOP = 4 -- franja del reloj (comun a todas las pestañas)
|
||||
local HDR_H = 30
|
||||
local TAB_TOP = HDR_TOP + HDR_H -- la barra de pestañas baja bajo el reloj
|
||||
local TAB_H = 24
|
||||
local CONTENT_TOP = TAB_TOP + TAB_H + 4 -- inicio del area de cada panel
|
||||
local BTN_H = 26
|
||||
local BTN_Y = 322 -- fila de botones de la pestaña Red
|
||||
local BTN_Y = CONTENT_TOP + 276 -- fila de botones de la pestaña Red (offset original del panel)
|
||||
|
||||
-- Interfaz de red a monitorizar (enp5s0 es la fisica activa) ------------------
|
||||
local NIF = "enp5s0"
|
||||
@@ -36,9 +39,15 @@ local NIF = "enp5s0"
|
||||
local MET = os.getenv("HOME") .. "/.config/conky/conky_widget/metric.sh"
|
||||
|
||||
-- Pestañas (Sistema por defecto) ---------------------------------------------
|
||||
local TABS = { "Sistema", "Red", "Docker" }
|
||||
local TABS = { "Sistema", "Red", "Docker", "Procs", "Devs" }
|
||||
local NTABS = #TABS
|
||||
local current_tab = 1
|
||||
|
||||
-- Filas clicables de la pestaña Procesos (se rellenan en cada frame de dibujo).
|
||||
-- Al clicar una fila se abre el comando PROC_CLICK con el PID de esa fila.
|
||||
local g_proc_rows = {}
|
||||
local PROC_CLICK = "kitty -e htop -p %s" -- %s = PID
|
||||
|
||||
-- Botones de la pestaña Red. bin se comprueba antes de lanzar; si falta,
|
||||
-- launch.sh avisa con notify-send.
|
||||
local BTNS = {
|
||||
@@ -253,28 +262,45 @@ local function graph(cr, x, y, w, h, series)
|
||||
cairo_close_path(cr)
|
||||
setcol(cr, col, 0.25); cairo_fill(cr)
|
||||
end
|
||||
cairo_set_line_width(cr, 1.3); setcol(cr, col)
|
||||
-- Lineas pixel-perfect: sin antialias y con las coordenadas ancladas al
|
||||
-- centro del pixel (floor + 0.5) para que el trazo de 1px caiga nitido
|
||||
-- sobre la rejilla en lugar de difuminarse entre dos columnas.
|
||||
cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE)
|
||||
cairo_set_line_width(cr, 1.0); setcol(cr, col)
|
||||
for i = 1, n do
|
||||
if i == 1 then cairo_move_to(cr, px(i), py(i)) else cairo_line_to(cr, px(i), py(i)) end
|
||||
local sx = math.floor(px(i)) + 0.5
|
||||
local sy = math.floor(py(i)) + 0.5
|
||||
if i == 1 then cairo_move_to(cr, sx, sy) else cairo_line_to(cr, sx, sy) end
|
||||
end
|
||||
cairo_stroke(cr)
|
||||
cairo_set_antialias(cr, CAIRO_ANTIALIAS_DEFAULT)
|
||||
end
|
||||
end
|
||||
|
||||
-- Barra de pestañas ----------------------------------------------------------
|
||||
local function draw_tabs(cr)
|
||||
local tw = W / 3
|
||||
for i = 1, 3 do
|
||||
local tw = W / NTABS
|
||||
for i = 1, NTABS do
|
||||
local x = (i - 1) * tw
|
||||
local active = (i == current_tab)
|
||||
setcol(cr, active and COL.tab_active or COL.tab_inactive)
|
||||
rrect(cr, x + 3, TAB_TOP, tw - 6, TAB_H, 5); cairo_fill(cr)
|
||||
rrect(cr, x + 2, TAB_TOP, tw - 4, TAB_H, 4); cairo_fill(cr)
|
||||
local lbl = TABS[i]
|
||||
text(cr, x + tw / 2 - #lbl * 12 * 0.6 / 2, TAB_TOP + 17, lbl,
|
||||
active and COL.white or COL.dim, 12, active)
|
||||
text(cr, x + tw / 2 - #lbl * 10 * 0.6 / 2, TAB_TOP + 16, lbl,
|
||||
active and COL.white or COL.dim, 10, active)
|
||||
end
|
||||
end
|
||||
|
||||
-- Reloj + fecha: franja superior comun a todas las pestañas (formato europeo)
|
||||
local function draw_clock(cr)
|
||||
local hora = str("${time %H:%M:%S}")
|
||||
local fecha = str("${time %d/%m/%Y}")
|
||||
local dia = str("${time %A}")
|
||||
text(cr, 12, HDR_TOP + 22, hora, COL.snow, 22, true)
|
||||
rtext(cr, W - 10, HDR_TOP + 13, dia, COL.cyan, 10)
|
||||
rtext(cr, W - 10, HDR_TOP + 26, fecha, COL.dim, 11)
|
||||
end
|
||||
|
||||
-- Botones de la pestaña Red --------------------------------------------------
|
||||
local function draw_buttons(cr)
|
||||
local bw = (W - 24) / 3
|
||||
@@ -290,7 +316,7 @@ end
|
||||
|
||||
-- Panel: Red -----------------------------------------------------------------
|
||||
local function draw_red(cr)
|
||||
local y = 46
|
||||
local y = CONTENT_TOP
|
||||
text(cr, 12, y + 10, "Interfaz " .. NIF, COL.dim, 11); y = y + 20
|
||||
|
||||
text(cr, 12, y + 10, "Down", COL.down, 12)
|
||||
@@ -344,7 +370,7 @@ end
|
||||
local function draw_sys(cr)
|
||||
local x = 10
|
||||
local gw = W - 20
|
||||
local y = 38
|
||||
local y = CONTENT_TOP
|
||||
|
||||
local function row(label, value, lc, gh)
|
||||
text(cr, x, y + 11, label, lc, 11, true)
|
||||
@@ -421,25 +447,118 @@ end
|
||||
|
||||
-- Panel: Docker --------------------------------------------------------------
|
||||
local function draw_docker(cr)
|
||||
local y = 50
|
||||
local running = str("${execi 3 docker ps -q 2>/dev/null | wc -l}")
|
||||
local total = str("${execi 10 docker ps -aq 2>/dev/null | wc -l}")
|
||||
text(cr, 12, y, "Contenedores", COL.text, 12, true)
|
||||
rtext(cr, W - 10, y, running .. " up / " .. total .. " total", COL.dim, 10)
|
||||
y = y + 22
|
||||
local y = CONTENT_TOP
|
||||
local count = str("${execi 3 " .. MET .. " docker_count}") -- "running/total"
|
||||
text(cr, 12, y + 14, "Contenedores", COL.snow, 12, true)
|
||||
rtext(cr, W - 10, y + 14, count .. " up", COL.green, 10)
|
||||
y = y + 28
|
||||
|
||||
local names = str("${execi 3 docker ps --format '{{.Names}}' 2>/dev/null | head -28}")
|
||||
if names == "" then
|
||||
text(cr, 16, y, "ninguno en marcha", COL.dim, 10)
|
||||
local list = str("${execi 3 " .. MET .. " docker_list}")
|
||||
if list == "" then
|
||||
text(cr, 16, y + 10, "ninguno en marcha", COL.dim, 10)
|
||||
return
|
||||
end
|
||||
for line in names:gmatch("[^\n]+") do
|
||||
text(cr, 16, y, "● " .. line, COL.green, 10)
|
||||
y = y + 15
|
||||
for line in list:gmatch("[^\n]+") do
|
||||
local name, status, image = line:match("^(.-)|(.-)|(.+)$")
|
||||
if name then
|
||||
local up = status:match("^Up") ~= nil
|
||||
local bad = status:match("UNHEALTHY") ~= nil
|
||||
text(cr, 14, y + 9, "● " .. name:sub(1, 16), up and COL.green or COL.dim, 10)
|
||||
rtext(cr, W - 10, y + 9, status:sub(1, 14),
|
||||
bad and COL.red or (up and COL.cyan or COL.dim), 9)
|
||||
y = y + 12
|
||||
text(cr, 24, y + 9, image:sub(1, 34), COL.dim, 9)
|
||||
y = y + 14
|
||||
end
|
||||
if y > H - 14 then break end
|
||||
end
|
||||
end
|
||||
|
||||
-- Panel: Procesos ------------------------------------------------------------
|
||||
local function draw_procesos(cr)
|
||||
local y = CONTENT_TOP
|
||||
g_proc_rows = {} -- se rellena con las filas clicables de TOP
|
||||
local total = str("${execi 2 " .. MET .. " nproc_count}")
|
||||
local running = str("${execi 2 " .. MET .. " nproc_running}")
|
||||
local threads = str("${execi 3 " .. MET .. " nthreads}")
|
||||
local load = str("${execi 2 " .. MET .. " load_avg}")
|
||||
|
||||
text(cr, 12, y + 11, "Procesos", COL.snow, 12, true)
|
||||
rtext(cr, W - 10, y + 11, total .. " (" .. running .. " run)", COL.green, 11); y = y + 17
|
||||
text(cr, 12, y + 11, "Hilos", COL.dim, 10)
|
||||
rtext(cr, W - 10, y + 11, threads, COL.text, 10); y = y + 14
|
||||
text(cr, 12, y + 11, "Carga 1/5/15m", COL.dim, 10)
|
||||
rtext(cr, W - 10, y + 11, load, COL.text, 10); y = y + 18
|
||||
|
||||
-- Tabla "<pid> <valor> <nombre>" por linea. El PID se pinta como enlace
|
||||
-- (azul) y la fila completa queda registrada en g_proc_rows para abrir
|
||||
-- PROC_CLICK al clicar.
|
||||
local function toplist(titulo, key, unidad, col)
|
||||
text(cr, 12, y + 10, titulo, COL.snow, 10, true)
|
||||
rtext(cr, W - 10, y + 10, unidad, COL.dim, 9); y = y + 14
|
||||
local out = str("${execi 3 " .. MET .. " " .. key .. "}")
|
||||
if out == "" then
|
||||
text(cr, 16, y + 9, "sin datos", COL.dim, 9); y = y + 13
|
||||
else
|
||||
for line in out:gmatch("[^\n]+") do
|
||||
local pid, val, name = line:match("^%s*(%d+)%s+([%d%.]+)%s+(.+)$")
|
||||
if pid and val and name then
|
||||
local ry = y
|
||||
text(cr, 16, y + 9, pid, COL.blue, 10) -- PID = enlace
|
||||
text(cr, 72, y + 9, name:sub(1, 13), COL.text, 10)
|
||||
rtext(cr, W - 10, y + 9, val, col, 10)
|
||||
g_proc_rows[#g_proc_rows + 1] = { y0 = ry, y1 = ry + 13, pid = pid }
|
||||
y = y + 13
|
||||
end
|
||||
if y > H - 12 then break end
|
||||
end
|
||||
end
|
||||
y = y + 4
|
||||
end
|
||||
toplist("TOP CPU", "top_cpu", "%cpu", COL.teal)
|
||||
toplist("TOP RAM", "top_ram", "%mem", COL.green)
|
||||
toplist("TOP I/O", "top_io", "KB/s", COL.orange)
|
||||
end
|
||||
|
||||
-- Panel: Devices -------------------------------------------------------------
|
||||
local function draw_devices(cr)
|
||||
local y = CONTENT_TOP
|
||||
|
||||
text(cr, 12, y + 10, "ALMACENAMIENTO", COL.snow, 10, true); y = y + 15
|
||||
local disks = str("${execi 10 " .. MET .. " disk_list}")
|
||||
for line in disks:gmatch("[^\n]+") do
|
||||
local name, size, model = line:match("^(%S+)%s+(%S+)%s*(.*)$")
|
||||
if name then
|
||||
text(cr, 16, y + 9, name .. " " .. (size or ""), COL.cyan, 10)
|
||||
if model and model ~= "" then rtext(cr, W - 10, y + 9, model:sub(1, 16), COL.dim, 9) end
|
||||
y = y + 13
|
||||
end
|
||||
end
|
||||
y = y + 6
|
||||
|
||||
text(cr, 12, y + 10, "RED", COL.snow, 10, true); y = y + 15
|
||||
local nets = str("${execi 5 " .. MET .. " net_ifaces}")
|
||||
for line in nets:gmatch("[^\n]+") do
|
||||
local ifc, addr = line:match("^(%S+)%s+(%S+)$")
|
||||
if ifc then
|
||||
text(cr, 16, y + 9, ifc, COL.green, 10)
|
||||
rtext(cr, W - 10, y + 9, addr or "", COL.text, 9); y = y + 13
|
||||
end
|
||||
end
|
||||
y = y + 6
|
||||
|
||||
text(cr, 12, y + 10, "USB", COL.snow, 10, true); y = y + 15
|
||||
local usb = str("${execi 10 " .. MET .. " usb_list}")
|
||||
if usb == "" then
|
||||
text(cr, 16, y + 9, "sin dispositivos USB", COL.dim, 9); y = y + 13
|
||||
else
|
||||
for line in usb:gmatch("[^\n]+") do
|
||||
text(cr, 16, y + 9, "• " .. line:sub(1, 40), COL.text, 9); y = y + 12
|
||||
if y > H - 12 then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Hook de dibujo principal ---------------------------------------------------
|
||||
function conky_draw()
|
||||
if conky_window == nil then return end
|
||||
@@ -462,13 +581,13 @@ function conky_draw()
|
||||
|
||||
setcol(cr, COL.bg, 0.86); rrect(cr, 0, 0, W, H, 10); cairo_fill(cr)
|
||||
|
||||
draw_clock(cr)
|
||||
draw_tabs(cr)
|
||||
if current_tab == 1 then
|
||||
draw_sys(cr)
|
||||
elseif current_tab == 2 then
|
||||
draw_red(cr)
|
||||
else
|
||||
draw_docker(cr)
|
||||
if current_tab == 1 then draw_sys(cr)
|
||||
elseif current_tab == 2 then draw_red(cr)
|
||||
elseif current_tab == 3 then draw_docker(cr)
|
||||
elseif current_tab == 4 then draw_procesos(cr)
|
||||
else draw_devices(cr)
|
||||
end
|
||||
|
||||
cairo_destroy(cr)
|
||||
@@ -496,8 +615,8 @@ function conky_mouse(event)
|
||||
|
||||
-- Barra de pestañas
|
||||
if y >= TAB_TOP and y <= TAB_TOP + TAB_H then
|
||||
local idx = math.floor(x / (W / 3)) + 1
|
||||
if idx >= 1 and idx <= 3 then current_tab = idx end
|
||||
local idx = math.floor(x / (W / NTABS)) + 1
|
||||
if idx >= 1 and idx <= NTABS then current_tab = idx end
|
||||
return
|
||||
end
|
||||
|
||||
@@ -512,4 +631,14 @@ function conky_mouse(event)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Pestaña Procesos: clic en una fila TOP abre el proceso (PROC_CLICK con su PID)
|
||||
if current_tab == 4 then
|
||||
for _, r in ipairs(g_proc_rows) do
|
||||
if y >= r.y0 and y <= r.y1 then
|
||||
os.execute(string.format(PROC_CLICK .. " >/dev/null 2>&1 &", r.pid))
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,5 +12,42 @@ case "$1" in
|
||||
cpu_temp) for h in /sys/class/hwmon/hwmon*; do
|
||||
[ "$(cat "$h/name" 2>/dev/null)" = coretemp ] && { cat "$h/temp1_input"; break; }
|
||||
done | awk '{printf "%d", $1/1000}' ;;
|
||||
|
||||
# --- Pestaña Procesos ---
|
||||
nproc_count) ps -e --no-headers 2>/dev/null | wc -l ;;
|
||||
nproc_running) awk '/^procs_running/{print $2}' /proc/stat 2>/dev/null ;;
|
||||
nthreads) ps -eLf --no-headers 2>/dev/null | wc -l ;;
|
||||
load_avg) awk '{printf "%s %s %s", $1, $2, $3}' /proc/loadavg 2>/dev/null ;;
|
||||
# "pid %valor nombre" por linea, mayor primero
|
||||
top_cpu) ps -eo pid=,pcpu=,comm= --sort=-pcpu 2>/dev/null | head -n 6 ;;
|
||||
top_ram) ps -eo pid=,pmem=,comm= --sort=-pmem 2>/dev/null | head -n 6 ;;
|
||||
# I/O por proceso: requiere pidstat (paquete sysstat); si falta, vacio.
|
||||
# Las columnas kB_rd/s y kB_wr/s se cuentan desde el final (Command = ultimo
|
||||
# campo, iodelay = NF-1, kB_ccwr/s = NF-2, kB_wr/s = NF-3, kB_rd/s = NF-4),
|
||||
# robusto frente a que la primera columna sea epoch o UID.
|
||||
top_io) command -v pidstat >/dev/null 2>&1 \
|
||||
&& pidstat -d 1 1 2>/dev/null \
|
||||
| awk 'NF>=6 && $NF!="Command" && $0 !~ /Average|Linux|^[[:space:]]*$/ \
|
||||
{ io=$(NF-4)+$(NF-3); if (io>0) printf "%s %.0f %s\n", $(NF-5), io, $NF }' \
|
||||
| sort -k2 -rn | head -n 5 \
|
||||
|| echo "" ;;
|
||||
|
||||
# --- Pestaña Devices ---
|
||||
# -e 7,11 excluye loops (snaps) y cdrom; solo discos reales
|
||||
disk_list) lsblk -dno NAME,SIZE,MODEL -e 7,11 2>/dev/null | head -n 8 ;;
|
||||
usb_list) lsusb 2>/dev/null \
|
||||
| sed -E 's/^Bus [0-9]+ Device [0-9]+: ID [0-9a-fA-F]+:[0-9a-fA-F]+ //' \
|
||||
| grep -viE 'root hub' | head -n 10 ;;
|
||||
# Interfaces fisicas/reales (excluye docker, bridges, veth, virbr)
|
||||
net_ifaces) ip -br -4 addr 2>/dev/null \
|
||||
| awk '$1 !~ /^(docker|br-|veth|virbr)/ && ($2=="UP" || $1=="lo") {printf "%s %s\n", $1, $3}' \
|
||||
| head -n 6 ;;
|
||||
|
||||
# --- Pestaña Docker ---
|
||||
# "nombre|estado|imagen" por contenedor. Estado y imagen abreviados.
|
||||
docker_list) docker ps --format '{{.Names}}|{{.Status}}|{{.Image}}' 2>/dev/null \
|
||||
| sed -E 's/Up ([0-9]+) seconds?/Up \1s/; s/Up ([0-9]+) minutes?/Up \1m/; s/Up ([0-9]+) hours?/Up \1h/; s/Up ([0-9]+) days?/Up \1d/; s/Up ([0-9]+) weeks?/Up \1w/; s/Up ([0-9]+) months?/Up \1mo/; s/Up Less than a second/Up 0s/; s/ \(healthy\)//; s/ \(unhealthy\)/ BAD/' \
|
||||
| head -n 14 ;;
|
||||
docker_count) printf "%s/%s" "$(docker ps -q 2>/dev/null | wc -l)" "$(docker ps -aq 2>/dev/null | wc -l)" ;;
|
||||
*) echo 0 ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user