Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e65770407c | |||
| 9e77d961f5 | |||
| 3933301cff | |||
| 9f87c9294d | |||
| 638d347bb0 | |||
| 8f3dbd0532 | |||
| 99bdf79a63 | |||
| a22d1f4cd2 | |||
| d8f395cb53 | |||
| f69c22039b | |||
| 2bda420728 | |||
| f604e22f6e |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nanometrics-agent"
|
name = "nanometrics-agent"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
|||||||
+33
-1
@@ -2,6 +2,13 @@ use nanometrics_agent::{config, metrics, payload, transport};
|
|||||||
use sysinfo::{Components, Networks, System};
|
use sysinfo::{Components, Networks, System};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
|
||||||
|
static RUNNING: AtomicBool = AtomicBool::new(true);
|
||||||
|
|
||||||
|
extern "C" fn handle_signal(_: libc::c_int) {
|
||||||
|
RUNNING.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
fn get_local_ip() -> String {
|
fn get_local_ip() -> String {
|
||||||
use std::net::UdpSocket;
|
use std::net::UdpSocket;
|
||||||
@@ -49,12 +56,17 @@ fn main() {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
libc::signal(libc::SIGTERM, handle_signal as libc::sighandler_t);
|
||||||
|
libc::signal(libc::SIGINT, handle_signal as libc::sighandler_t);
|
||||||
|
}
|
||||||
|
|
||||||
let mut last_slow = Instant::now();
|
let mut last_slow = Instant::now();
|
||||||
let mut last_medium = Instant::now();
|
let mut last_medium = Instant::now();
|
||||||
let mut first_medium = true;
|
let mut first_medium = true;
|
||||||
let mut first_slow = true;
|
let mut first_slow = true;
|
||||||
|
|
||||||
loop {
|
while RUNNING.load(Ordering::Relaxed) {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
while let Ok(transport::mqtt::MqttIncoming::ConfigUpdate(data)) = incoming_rx.try_recv() {
|
while let Ok(transport::mqtt::MqttIncoming::ConfigUpdate(data)) = incoming_rx.try_recv() {
|
||||||
@@ -134,4 +146,24 @@ fn main() {
|
|||||||
|
|
||||||
std::thread::sleep(Duration::from_secs(2));
|
std::thread::sleep(Duration::from_secs(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Déconnexion propre : notifier le serveur avant de quitter
|
||||||
|
let offline = serde_json::to_string(&payload::AgentMetrics {
|
||||||
|
hostname: hostname.clone(),
|
||||||
|
ip: ip.clone(),
|
||||||
|
status: "offline".to_string(),
|
||||||
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}).unwrap_or_default();
|
||||||
|
|
||||||
|
if let Some(ref udp) = udp_sender {
|
||||||
|
udp.send(&offline);
|
||||||
|
}
|
||||||
|
if let Some(ref client) = mqtt_client {
|
||||||
|
transport::mqtt::publish_status(
|
||||||
|
client, &cfg.protocols.mqtt.topic_base, &hostname, "offline",
|
||||||
|
);
|
||||||
|
std::thread::sleep(Duration::from_millis(200)); // laisser le temps au broker de recevoir
|
||||||
|
let _ = client.disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub struct AgentMetrics {
|
|||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
pub ip: String,
|
pub ip: String,
|
||||||
pub status: String,
|
pub status: String,
|
||||||
|
#[serde(default)]
|
||||||
pub version: String,
|
pub version: String,
|
||||||
pub cpu_percent: Option<f32>,
|
pub cpu_percent: Option<f32>,
|
||||||
pub memory_used: Option<u64>,
|
pub memory_used: Option<u64>,
|
||||||
|
|||||||
@@ -78,3 +78,8 @@ pub fn publish_metrics(client: &Client, topic_base: &str, hostname: &str, json:
|
|||||||
let topic = format!("{}/{}/metrics", topic_base, hostname);
|
let topic = format!("{}/{}/metrics", topic_base, hostname);
|
||||||
let _ = client.publish(topic, QoS::AtMostOnce, false, json);
|
let _ = client.publish(topic, QoS::AtMostOnce, false, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn publish_status(client: &Client, topic_base: &str, hostname: &str, status: &str) {
|
||||||
|
let topic = format!("{}/{}/status", topic_base, hostname);
|
||||||
|
let _ = client.publish(topic, QoS::AtLeastOnce, true, status);
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ fn test_serialize_json_complet() {
|
|||||||
temperature: None,
|
temperature: None,
|
||||||
smart: None,
|
smart: None,
|
||||||
status: "online".to_string(),
|
status: "online".to_string(),
|
||||||
|
version: "0.0.0".to_string(),
|
||||||
};
|
};
|
||||||
let json = serde_json::to_string(&m).unwrap();
|
let json = serde_json::to_string(&m).unwrap();
|
||||||
assert!(json.contains("\"hostname\":\"srv-01\""));
|
assert!(json.contains("\"hostname\":\"srv-01\""));
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -73,12 +73,12 @@
|
|||||||
<img id="pop-icon-img" src="" alt="" style="display:none">
|
<img id="pop-icon-img" src="" alt="" style="display:none">
|
||||||
<div class="agent-icon-overlay"><i class="fa-solid fa-camera"></i><span>Changer</span></div>
|
<div class="agent-icon-overlay"><i class="fa-solid fa-camera"></i><span>Changer</span></div>
|
||||||
</div>
|
</div>
|
||||||
<input type="file" id="icon-upload" accept=".svg,.jpg,.jpeg,.png,.webp" style="display:none">
|
<input type="file" id="icon-upload" accept=".jpg,.jpeg,.png,.webp" style="display:none">
|
||||||
<div style="flex:1">
|
<div style="flex:1">
|
||||||
<div class="pop-host" id="pop-host">—</div>
|
<div class="pop-host" id="pop-host">—</div>
|
||||||
<div class="pop-ip" id="pop-ip">—</div>
|
<div class="pop-ip" id="pop-ip">—</div>
|
||||||
<div style="font-size:10px;color:var(--ink-4);font-family:var(--font-terminal);margin-top:2px">
|
<div id="icon-hint" style="font-size:10px;color:var(--ink-4);font-family:var(--font-terminal);margin-top:2px">
|
||||||
Cliquer sur l'icône pour personnaliser · SVG JPG PNG WEBP · max 128×128 px
|
Cliquer sur l'icône pour personnaliser · JPG PNG WEBP · max 128×128 px
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="pop-led" id="pop-led"></div>
|
<div class="pop-led" id="pop-led"></div>
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ const Grid = (() => {
|
|||||||
const iconContent = `<img src="${API.iconUrl(id)}" alt=""
|
const iconContent = `<img src="${API.iconUrl(id)}" alt=""
|
||||||
style="width:100%;height:100%;object-fit:cover;border-radius:7px"
|
style="width:100%;height:100%;object-fit:cover;border-radius:7px"
|
||||||
onerror="this.style.display='none';this.nextSibling.style.display='flex'">
|
onerror="this.style.display='none';this.nextSibling.style.display='flex'">
|
||||||
<span style="display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:var(--accent)">
|
<span style="display:none;align-items:center;justify-content:center;width:100%;height:100%;color:var(--accent)">
|
||||||
<i class="fa-solid fa-server"></i></span>`;
|
<i class="fa-solid fa-server"></i></span>`;
|
||||||
|
|
||||||
return `<div class="tile ${sc}" id="tile-${id}" onclick="Popups.showDetail('${esc(id)}')">
|
return `<div class="tile ${sc}" id="tile-${id}" onclick="Popups.showDetail('${esc(id)}')">
|
||||||
@@ -87,7 +87,7 @@ const Grid = (() => {
|
|||||||
<div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div>
|
<div class="g-ico" data-tip="Disque"><i class="fa-solid fa-hard-drive"></i></div>
|
||||||
<div class="g-bar"><div class="g-fill ${offline ? '' : (diskPct >= (App.serverConfig?.warn_disk ?? 75) ? 'w' : '')}"
|
<div class="g-bar"><div class="g-fill ${offline ? '' : (diskPct >= (App.serverConfig?.warn_disk ?? 75) ? 'w' : '')}"
|
||||||
style="width:${offline ? 0 : (diskPct ?? 0).toFixed(0)}%"></div></div>
|
style="width:${offline ? 0 : (diskPct ?? 0).toFixed(0)}%"></div></div>
|
||||||
<span class="g-val">${offline ? '—' : fmtPct(diskPct)}</span>
|
<span class="g-val">${offline ? '—' : (metrics?.hdd_used && metrics?.hdd_total ? fmt(metrics.hdd_used) + '/' + fmt(metrics.hdd_total) : '—')}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tile-foot">
|
<div class="tile-foot">
|
||||||
@@ -124,7 +124,7 @@ const Grid = (() => {
|
|||||||
function refresh(agents) {
|
function refresh(agents) {
|
||||||
agents.forEach(a => {
|
agents.forEach(a => {
|
||||||
if (!_agents.has(a.id)) {
|
if (!_agents.has(a.id)) {
|
||||||
_agents.set(a.id, { agent: a, metrics: null });
|
_agents.set(a.id, { agent: a, metrics: a.last_metrics || null });
|
||||||
} else {
|
} else {
|
||||||
_agents.get(a.id).agent = a;
|
_agents.get(a.id).agent = a;
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-2
@@ -30,8 +30,27 @@ const Popups = (() => {
|
|||||||
document.getElementById('icon-upload').onchange = async (e) => {
|
document.getElementById('icon-upload').onchange = async (e) => {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
await API.uploadIcon(agentId, file);
|
const hint = document.getElementById('icon-hint');
|
||||||
img.src = API.iconUrl(agentId) + '?t=' + Date.now();
|
try {
|
||||||
|
await API.uploadIcon(agentId, file);
|
||||||
|
const ts = '?t=' + Date.now();
|
||||||
|
img.src = API.iconUrl(agentId) + ts;
|
||||||
|
img.style.display = 'block';
|
||||||
|
document.getElementById('pop-icon-fa').style.display = 'none';
|
||||||
|
const tileImg = document.querySelector(`#tile-${CSS.escape(agentId)} .t-icon img`);
|
||||||
|
if (tileImg) tileImg.src = API.iconUrl(agentId) + ts;
|
||||||
|
} catch (err) {
|
||||||
|
if (hint) {
|
||||||
|
hint.style.color = 'var(--err)';
|
||||||
|
hint.textContent = 'Erreur : ' + (err.message || 'téléversement échoué');
|
||||||
|
setTimeout(() => {
|
||||||
|
hint.style.color = '';
|
||||||
|
hint.textContent = 'Cliquer sur l\'icône pour personnaliser · JPG PNG WEBP · max 128×128 px';
|
||||||
|
}, 4000);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
e.target.value = '';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Uptime
|
// Uptime
|
||||||
|
|||||||
+30
-3
@@ -112,10 +112,33 @@ ok "Répertoire créé"
|
|||||||
# ── 7. Écrire config.toml ─────────────────────────────────────────────────────
|
# ── 7. Écrire config.toml ─────────────────────────────────────────────────────
|
||||||
echo "[3/5] Écriture de $CONFIG_FILE"
|
echo "[3/5] Écriture de $CONFIG_FILE"
|
||||||
|
|
||||||
# Ne pas écraser une config existante (upgrade)
|
OVERWRITE_CONFIG="${OVERWRITE_CONFIG:-}"
|
||||||
|
WRITE_CONFIG=true
|
||||||
|
|
||||||
if [ -f "$CONFIG_FILE" ]; then
|
if [ -f "$CONFIG_FILE" ]; then
|
||||||
warn "config.toml déjà présent — conservé tel quel"
|
if [ "${OVERWRITE_CONFIG}" = "true" ]; then
|
||||||
else
|
warn "OVERWRITE_CONFIG=true — config.toml sera écrasé"
|
||||||
|
WRITE_CONFIG=true
|
||||||
|
elif [ -t 0 ]; then
|
||||||
|
# Mode interactif (bash local, pas curl | bash)
|
||||||
|
echo ""
|
||||||
|
warn "Un config.toml existe déjà :"
|
||||||
|
echo " $CONFIG_FILE"
|
||||||
|
printf " Écraser la configuration existante ? [o/N] : "
|
||||||
|
read -r _ANS
|
||||||
|
if [[ "$_ANS" =~ ^[Oo]$ ]]; then
|
||||||
|
WRITE_CONFIG=true
|
||||||
|
else
|
||||||
|
ok "config.toml conservé"
|
||||||
|
WRITE_CONFIG=false
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "config.toml déjà présent — conservé (relancez avec OVERWRITE_CONFIG=true pour écraser)"
|
||||||
|
WRITE_CONFIG=false
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$WRITE_CONFIG" = "true" ]; then
|
||||||
cat > "$CONFIG_FILE" << TOML
|
cat > "$CONFIG_FILE" << TOML
|
||||||
[server]
|
[server]
|
||||||
ip = "$SERVER_IP"
|
ip = "$SERVER_IP"
|
||||||
@@ -165,6 +188,10 @@ TOML
|
|||||||
ok "config.toml créé"
|
ok "config.toml créé"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# S'assurer que le fichier est toujours lisible (cas d'un config existant en 640)
|
||||||
|
chmod 644 "$CONFIG_FILE" 2>/dev/null || true
|
||||||
|
chmod 755 "$CONFIG_DIR"
|
||||||
|
|
||||||
# ── 8. Installer le fichier service ──────────────────────────────────────────
|
# ── 8. Installer le fichier service ──────────────────────────────────────────
|
||||||
echo "[4/5] Installation du service systemd"
|
echo "[4/5] Installation du service systemd"
|
||||||
curl -fsSL -o "$SERVICE_FILE" "$SERVICE_URL"
|
curl -fsSL -o "$SERVICE_FILE" "$SERVICE_URL"
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ RestartSec=5
|
|||||||
|
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
ConfigurationDirectory=nanometrics
|
ConfigurationDirectory=nanometrics
|
||||||
ConfigurationDirectoryMode=0750
|
ConfigurationDirectoryMode=0755
|
||||||
|
|
||||||
ProtectSystem=strict
|
ProtectSystem=strict
|
||||||
ProtectHome=read-only
|
ProtectHome=read-only
|
||||||
|
|||||||
+3
-1
@@ -25,7 +25,9 @@ DESCRIPTION="${2:-Release $TAG}"
|
|||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
CARGO_TOML="$ROOT/agent/Cargo.toml"
|
CARGO_TOML="$ROOT/agent/Cargo.toml"
|
||||||
|
|
||||||
# ── 1. Compiler pour toutes les cibles supportées ──────────────────────────
|
mkdir -p "$ROOT/dist"
|
||||||
|
|
||||||
|
# ── 1. Compiler l'agent pour toutes les cibles supportées ────────────────
|
||||||
echo "=== Compilation de l'agent ==="
|
echo "=== Compilation de l'agent ==="
|
||||||
TARGETS=("x86_64-unknown-linux-musl" "aarch64-unknown-linux-musl")
|
TARGETS=("x86_64-unknown-linux-musl" "aarch64-unknown-linux-musl")
|
||||||
LABELS=("linux-amd64" "linux-arm64")
|
LABELS=("linux-amd64" "linux-arm64")
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+4
-5
@@ -5,10 +5,9 @@ RUN go mod download
|
|||||||
COPY . .
|
COPY . .
|
||||||
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o nanometrics-server .
|
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o nanometrics-server .
|
||||||
|
|
||||||
FROM alpine:3.19
|
FROM scratch
|
||||||
RUN apk add --no-cache ca-certificates
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
WORKDIR /app
|
COPY --from=builder /app/nanometrics-server /nanometrics-server
|
||||||
COPY --from=builder /app/nanometrics-server .
|
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
EXPOSE 8080 9999/udp
|
EXPOSE 8080 9999/udp
|
||||||
CMD ["./nanometrics-server"]
|
CMD ["/nanometrics-server"]
|
||||||
|
|||||||
@@ -128,6 +128,72 @@ func (d *DB) GetAgents() ([]models.Agent, error) {
|
|||||||
return agents, nil
|
return agents, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DB) GetLastMetrics(agentID string) (*models.AgentMetrics, error) {
|
||||||
|
var cpu, temperature *float64
|
||||||
|
var memUsed, memFree, memTotal, hddUsed, hddFree, hddTotal *int64
|
||||||
|
var uptime, netRX, netTX *int64
|
||||||
|
var smartPassed, smartTemp, smartRealloc, smartHours, smartWear *int64
|
||||||
|
|
||||||
|
// Chaque sous-requête prend la dernière valeur NON NULL de sa colonne.
|
||||||
|
// Nécessaire car les paquets rapides (2s) ne contiennent pas les métriques
|
||||||
|
// lentes (disque, smart) qui sont envoyées toutes les 60s.
|
||||||
|
err := d.conn.QueryRow(`
|
||||||
|
SELECT
|
||||||
|
(SELECT cpu_percent FROM metrics WHERE agent_id=? AND cpu_percent IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT memory_used FROM metrics WHERE agent_id=? AND memory_used IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT memory_free FROM metrics WHERE agent_id=? AND memory_free IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT memory_total FROM metrics WHERE agent_id=? AND memory_total IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT hdd_used FROM metrics WHERE agent_id=? AND hdd_used IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT hdd_free FROM metrics WHERE agent_id=? AND hdd_free IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT hdd_total FROM metrics WHERE agent_id=? AND hdd_total IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT uptime FROM metrics WHERE agent_id=? AND uptime IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT network_rx FROM metrics WHERE agent_id=? AND network_rx IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT network_tx FROM metrics WHERE agent_id=? AND network_tx IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT temperature FROM metrics WHERE agent_id=? AND temperature IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT smart_passed FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT smart_temp FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT smart_realloc FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT smart_hours FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1),
|
||||||
|
(SELECT smart_wear FROM metrics WHERE agent_id=? AND smart_passed IS NOT NULL ORDER BY ts DESC LIMIT 1)`,
|
||||||
|
agentID, agentID, agentID, agentID,
|
||||||
|
agentID, agentID, agentID,
|
||||||
|
agentID, agentID, agentID, agentID,
|
||||||
|
agentID, agentID, agentID, agentID, agentID).
|
||||||
|
Scan(&cpu, &memUsed, &memFree, &memTotal,
|
||||||
|
&hddUsed, &hddFree, &hddTotal,
|
||||||
|
&uptime, &netRX, &netTX, &temperature,
|
||||||
|
&smartPassed, &smartTemp, &smartRealloc, &smartHours, &smartWear)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m := &models.AgentMetrics{
|
||||||
|
CPUPercent: cpu,
|
||||||
|
MemoryUsed: memUsed,
|
||||||
|
MemoryFree: memFree,
|
||||||
|
MemoryTotal: memTotal,
|
||||||
|
HDDUsed: hddUsed,
|
||||||
|
HDDFree: hddFree,
|
||||||
|
HDDTotal: hddTotal,
|
||||||
|
Uptime: uptime,
|
||||||
|
NetworkRX: netRX,
|
||||||
|
NetworkTX: netTX,
|
||||||
|
Temperature: temperature,
|
||||||
|
}
|
||||||
|
if smartPassed != nil {
|
||||||
|
m.Smart = &models.SmartMetrics{
|
||||||
|
Passed: *smartPassed == 1,
|
||||||
|
Temperature: smartTemp,
|
||||||
|
ReallocatedSectors: smartRealloc,
|
||||||
|
PowerOnHours: smartHours,
|
||||||
|
WearLevel: smartWear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *DB) GetMetricsHistory(agentID string, from, to int64) ([]map[string]interface{}, error) {
|
func (d *DB) GetMetricsHistory(agentID string, from, to int64) ([]map[string]interface{}, error) {
|
||||||
rows, err := d.conn.Query(`
|
rows, err := d.conn.Query(`
|
||||||
SELECT ts, cpu_percent, memory_used, memory_total, hdd_used, hdd_total
|
SELECT ts, cpu_percent, memory_used, memory_total, hdd_used, hdd_total
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
server:
|
server:
|
||||||
build: .
|
build:
|
||||||
|
context: .
|
||||||
|
pull: false
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
UDP_ADDR: "0.0.0.0:9999"
|
UDP_ADDR: "0.0.0.0:9999"
|
||||||
@@ -16,6 +17,7 @@ services:
|
|||||||
|
|
||||||
dashboard:
|
dashboard:
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
|
pull_policy: if_not_present
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ func AgentsHandler(database *db.DB) http.HandlerFunc {
|
|||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
for i := range agents {
|
||||||
|
agents[i].LastMetrics, _ = database.GetLastMetrics(agents[i].ID)
|
||||||
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(agents)
|
json.NewEncoder(w).Encode(agents)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"image"
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
"image/png"
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
@@ -11,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/disintegration/imaging"
|
"github.com/disintegration/imaging"
|
||||||
"github.com/user/nanometrics/server/db"
|
"github.com/user/nanometrics/server/db"
|
||||||
|
_ "golang.org/x/image/webp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxIconSize = 128
|
const maxIconSize = 128
|
||||||
|
|||||||
Executable
BIN
Binary file not shown.
@@ -2,6 +2,7 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
client_max_body_size 10m;
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://server:8080;
|
proxy_pass http://server:8080;
|
||||||
|
|||||||
Reference in New Issue
Block a user