Compare commits
@@ -0,0 +1,9 @@
|
|||||||
|
[target.x86_64-unknown-linux-musl]
|
||||||
|
linker = "musl-gcc"
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-musl]
|
||||||
|
linker = "aarch64-linux-gnu-gcc"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
CC_aarch64_unknown_linux_musl = "aarch64-linux-gnu-gcc"
|
||||||
|
AR_aarch64_unknown_linux_musl = "aarch64-linux-gnu-ar"
|
||||||
@@ -10,4 +10,10 @@ server/*.test
|
|||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
config.toml
|
# Config locaux (contiennent des IPs/secrets)
|
||||||
|
agent/config.toml
|
||||||
|
server/config.toml
|
||||||
|
|
||||||
|
# Sauf les configs cargo (pas de secrets)
|
||||||
|
!.cargo/config.toml
|
||||||
|
!agent/.cargo/config.toml
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[target.x86_64-unknown-linux-musl]
|
||||||
|
linker = "musl-gcc"
|
||||||
|
|
||||||
|
[target.aarch64-unknown-linux-musl]
|
||||||
|
linker = "aarch64-linux-gnu-gcc"
|
||||||
|
|
||||||
|
[env]
|
||||||
|
CC_aarch64_unknown_linux_musl = "aarch64-linux-gnu-gcc"
|
||||||
|
AR_aarch64_unknown_linux_musl = "aarch64-linux-gnu-ar"
|
||||||
@@ -38,9 +38,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.10.1"
|
version = "0.9.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
|
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -97,21 +97,6 @@ version = "0.1.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
||||||
dependencies = [
|
|
||||||
"foreign-types-shared",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "foreign-types-shared"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.32"
|
version = "0.3.32"
|
||||||
@@ -142,6 +127,17 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
@@ -247,13 +243,14 @@ checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nanometrics-agent"
|
name = "nanometrics-agent"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"libc",
|
||||||
"rumqttc",
|
"rumqttc",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -262,23 +259,6 @@ dependencies = [
|
|||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "native-tls"
|
|
||||||
version = "0.2.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"openssl",
|
|
||||||
"openssl-probe",
|
|
||||||
"openssl-sys",
|
|
||||||
"schannel",
|
|
||||||
"security-framework",
|
|
||||||
"security-framework-sys",
|
|
||||||
"tempfile",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
@@ -294,48 +274,11 @@ version = "1.21.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl"
|
|
||||||
version = "0.10.80"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"cfg-if",
|
|
||||||
"foreign-types",
|
|
||||||
"libc",
|
|
||||||
"openssl-macros",
|
|
||||||
"openssl-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-macros"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openssl-probe"
|
name = "openssl-probe"
|
||||||
version = "0.2.1"
|
version = "0.1.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
|
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "openssl-sys"
|
|
||||||
version = "0.9.116"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4"
|
|
||||||
dependencies = [
|
|
||||||
"cc",
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
"vcpkg",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
@@ -343,12 +286,6 @@ version = "0.2.17"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pkg-config"
|
|
||||||
version = "0.3.33"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.2.37"
|
version = "0.2.37"
|
||||||
@@ -383,6 +320,20 @@ version = "6.0.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"libc",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rumqttc"
|
name = "rumqttc"
|
||||||
version = "0.24.0"
|
version = "0.24.0"
|
||||||
@@ -393,10 +344,12 @@ dependencies = [
|
|||||||
"flume",
|
"flume",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"log",
|
"log",
|
||||||
"native-tls",
|
"rustls-native-certs",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"rustls-webpki",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-native-tls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -409,7 +362,63 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.22.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-native-certs"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5"
|
||||||
|
dependencies = [
|
||||||
|
"openssl-probe",
|
||||||
|
"rustls-pemfile",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"schannel",
|
||||||
|
"security-framework",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.102.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||||
|
dependencies = [
|
||||||
|
"ring",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -418,7 +427,7 @@ version = "0.1.29"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -429,9 +438,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "3.7.0"
|
version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"core-foundation",
|
"core-foundation",
|
||||||
@@ -527,7 +536,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -539,6 +548,12 @@ dependencies = [
|
|||||||
"lock_api",
|
"lock_api",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "subtle"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
@@ -571,10 +586,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fastrand",
|
"fastrand",
|
||||||
"getrandom",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -609,7 +624,7 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -624,12 +639,13 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-native-tls"
|
name = "tokio-rustls"
|
||||||
version = "0.3.1"
|
version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"native-tls",
|
"rustls",
|
||||||
|
"rustls-pki-types",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -687,10 +703,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "untrusted"
|
||||||
version = "0.2.15"
|
version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
@@ -797,6 +813,15 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.61.2"
|
version = "0.61.2"
|
||||||
@@ -973,6 +998,12 @@ dependencies = [
|
|||||||
"wasmparser",
|
"wasmparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
version = "1.0.21"
|
version = "1.0.21"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "nanometrics-agent"
|
name = "nanometrics-agent"
|
||||||
version = "0.1.0"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -19,10 +19,11 @@ codegen-units = 1
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sysinfo = { version = "0.30", default-features = false }
|
sysinfo = { version = "0.30", default-features = false }
|
||||||
|
libc = "0.2"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
rumqttc = { version = "0.24", default-features = false, features = ["use-native-tls"] }
|
rumqttc = { version = "0.24", default-features = false, features = ["use-rustls"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
use nanometrics_agent::{config, metrics, payload, transport};
|
use nanometrics_agent::{config, metrics, payload, transport};
|
||||||
use sysinfo::{Components, Disks, 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;
|
||||||
@@ -34,7 +41,6 @@ fn main() {
|
|||||||
|
|
||||||
let mut sys = System::new();
|
let mut sys = System::new();
|
||||||
let mut networks = Networks::new_with_refreshed_list();
|
let mut networks = Networks::new_with_refreshed_list();
|
||||||
let mut disks = Disks::new_with_refreshed_list();
|
|
||||||
let mut components = Components::new_with_refreshed_list();
|
let mut components = Components::new_with_refreshed_list();
|
||||||
|
|
||||||
let udp_sender = if cfg.protocols.udp.enabled {
|
let udp_sender = if cfg.protocols.udp.enabled {
|
||||||
@@ -50,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() {
|
||||||
@@ -69,6 +80,7 @@ fn main() {
|
|||||||
hostname: hostname.clone(),
|
hostname: hostname.clone(),
|
||||||
ip: ip.clone(),
|
ip: ip.clone(),
|
||||||
status: "online".to_string(),
|
status: "online".to_string(),
|
||||||
|
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -102,9 +114,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if first_slow || now.duration_since(last_slow).as_secs() >= 60 {
|
if first_slow || now.duration_since(last_slow).as_secs() >= 60 {
|
||||||
disks.refresh();
|
|
||||||
if cfg.metrics.disk.udp || cfg.metrics.disk.mqtt {
|
if cfg.metrics.disk.udp || cfg.metrics.disk.mqtt {
|
||||||
let (used, free, total) = metrics::disk::get(&disks);
|
let (used, free, total) = metrics::disk::get();
|
||||||
m.hdd_used = Some(used);
|
m.hdd_used = Some(used);
|
||||||
m.hdd_free = Some(free);
|
m.hdd_free = Some(free);
|
||||||
m.hdd_total = Some(total);
|
m.hdd_total = Some(total);
|
||||||
@@ -135,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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
use sysinfo::Disks;
|
use std::mem::MaybeUninit;
|
||||||
|
|
||||||
pub fn get(disks: &Disks) -> (u64, u64, u64) {
|
/// Retourne (used, free, total) en octets pour le système de fichiers racine "/".
|
||||||
for disk in disks.list() {
|
/// Utilise statvfs() directement pour correspondre exactement aux chiffres de `df` :
|
||||||
let mount = disk.mount_point().to_string_lossy();
|
/// - total = f_blocks × f_frsize
|
||||||
if mount == "/" {
|
/// - used = (f_blocks − f_bfree) × f_frsize (blocs effectivement écrits)
|
||||||
let total = disk.total_space();
|
/// - free = f_bavail × f_frsize (disponible pour utilisateurs non-root)
|
||||||
let free = disk.available_space();
|
pub fn get() -> (u64, u64, u64) {
|
||||||
let used = total.saturating_sub(free);
|
let path = b"/\0";
|
||||||
return (used, free, total);
|
let mut stat = MaybeUninit::<libc::statvfs>::uninit();
|
||||||
}
|
let ret = unsafe { libc::statvfs(path.as_ptr() as *const libc::c_char, stat.as_mut_ptr()) };
|
||||||
|
if ret != 0 {
|
||||||
|
return (0, 0, 0);
|
||||||
}
|
}
|
||||||
if let Some(disk) = disks.list().first() {
|
let stat = unsafe { stat.assume_init() };
|
||||||
let total = disk.total_space();
|
let bsize = stat.f_frsize as u64;
|
||||||
let free = disk.available_space();
|
let total = stat.f_blocks.saturating_mul(bsize);
|
||||||
return (total.saturating_sub(free), free, total);
|
let used = stat.f_blocks.saturating_sub(stat.f_bfree).saturating_mul(bsize);
|
||||||
}
|
let free = stat.f_bavail.saturating_mul(bsize);
|
||||||
(0, 0, 0)
|
(used, free, total)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use sysinfo::Disks;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_disk_coherent() {
|
fn test_disk_coherent() {
|
||||||
let disks = Disks::new_with_refreshed_list();
|
let (used, free, total) = get();
|
||||||
let (used, free, total) = get(&disks);
|
eprintln!("résultat statvfs : used={used} free={free} total={total}");
|
||||||
if total > 0 {
|
if total > 0 {
|
||||||
assert!(used + free <= total + 1024, "used + free > total");
|
assert!(used + free <= total + 1024 * 1024, "used + free > total");
|
||||||
|
assert!(total > 0, "total doit être > 0");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ 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 cpu_percent: Option<f32>,
|
pub cpu_percent: Option<f32>,
|
||||||
pub memory_used: Option<u64>,
|
pub memory_used: Option<u64>,
|
||||||
pub memory_free: Option<u64>,
|
pub memory_free: 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\""));
|
||||||
|
|||||||
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -115,9 +115,15 @@ body{background:var(--bg-1);color:var(--ink-1);font-family:var(--font-ui);font-s
|
|||||||
.g-bar{flex:1;height:5px;border-radius:3px;background:var(--bg-1);overflow:hidden}
|
.g-bar{flex:1;height:5px;border-radius:3px;background:var(--bg-1);overflow:hidden}
|
||||||
.g-fill{height:100%;border-radius:3px;background:var(--ok);transition:width .3s}
|
.g-fill{height:100%;border-radius:3px;background:var(--ok);transition:width .3s}
|
||||||
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
.g-fill.w{background:var(--warn)}.g-fill.e{background:var(--err)}
|
||||||
.g-val{font-family:var(--font-mono);font-size:11px;color:var(--ink-2);width:34px;text-align:right}
|
.g-val{font-family:var(--font-mono);font-size:11px;color:var(--ink-2);
|
||||||
|
min-width:34px;white-space:nowrap;flex-shrink:0;text-align:right}
|
||||||
.tile-foot{font-family:var(--font-terminal);font-size:10px;color:var(--ink-4);
|
.tile-foot{font-family:var(--font-terminal);font-size:10px;color:var(--ink-4);
|
||||||
display:flex;align-items:center;gap:5px;user-select:none}
|
display:flex;align-items:center;justify-content:space-between;user-select:none}
|
||||||
|
.tile-foot-info{display:flex;align-items:center;gap:5px;min-width:0;overflow:hidden}
|
||||||
|
.btn-del-agent{border:none;background:transparent;cursor:pointer;flex-shrink:0;
|
||||||
|
color:var(--ink-5);font-size:11px;padding:2px 4px;border-radius:4px;
|
||||||
|
line-height:1;transition:color .15s,background .15s;user-select:none}
|
||||||
|
.btn-del-agent:hover{color:var(--err);background:color-mix(in srgb,var(--err) 12%,transparent)}
|
||||||
|
|
||||||
/* FOOTER */
|
/* FOOTER */
|
||||||
.footer{background:var(--bg-0);border-top:1px solid var(--border-2);height:26px;
|
.footer{background:var(--bg-0);border-top:1px solid var(--border-2);height:26px;
|
||||||
|
|||||||
|
After Width: | Height: | Size: 173 B |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 235 B |
|
After Width: | Height: | Size: 306 B |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 576 B |
|
After Width: | Height: | Size: 768 B |
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Fond arrondi -->
|
||||||
|
<rect width="64" height="64" rx="14" fill="#282828"/>
|
||||||
|
<!-- Cercle accent -->
|
||||||
|
<circle cx="32" cy="32" r="22" fill="none" stroke="#fe8019" stroke-width="4"/>
|
||||||
|
<!-- Barre CPU style jauge -->
|
||||||
|
<rect x="18" y="28" width="28" height="4" rx="2" fill="#504945"/>
|
||||||
|
<rect x="18" y="28" width="18" height="4" rx="2" fill="#fe8019"/>
|
||||||
|
<!-- Point LED vert -->
|
||||||
|
<circle cx="43" cy="21" r="4" fill="#b8bb26"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 498 B |
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "Nanometrics",
|
||||||
|
"short_name": "Nanometrics",
|
||||||
|
"description": "Tableau de bord de surveillance système",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#282828",
|
||||||
|
"theme_color": "#fe8019",
|
||||||
|
"icons": [
|
||||||
|
{ "src": "favicon-192.png", "sizes": "192x192", "type": "image/png" },
|
||||||
|
{ "src": "favicon-512.png", "sizes": "512x512", "type": "image/png" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -4,6 +4,13 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Nanometrics</title>
|
<title>Nanometrics</title>
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon/favicon.ico">
|
||||||
|
<link rel="icon" type="image/svg+xml" href="favicon/favicon.svg">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16.png">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="favicon/favicon-180.png">
|
||||||
|
<link rel="manifest" href="favicon/site.webmanifest">
|
||||||
|
<meta name="theme-color" content="#282828">
|
||||||
<link rel="stylesheet" href="vendor/fontawesome/css/all.min.css">
|
<link rel="stylesheet" href="vendor/fontawesome/css/all.min.css">
|
||||||
<link rel="stylesheet" href="css/app.css">
|
<link rel="stylesheet" href="css/app.css">
|
||||||
</head>
|
</head>
|
||||||
@@ -66,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>
|
||||||
@@ -147,6 +154,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- DIALOG SUPPRESSION AGENT -->
|
||||||
|
<div class="overlay" id="overlay-del" style="display:none;z-index:400" onclick="if(event.target===this)this.style.display='none'">
|
||||||
|
<div class="popup" style="width:360px;max-width:96vw" onclick="event.stopPropagation()">
|
||||||
|
<div style="padding:20px 20px 0">
|
||||||
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:14px">
|
||||||
|
<div style="width:36px;height:36px;border-radius:8px;background:color-mix(in srgb,var(--err) 15%,transparent);display:flex;align-items:center;justify-content:center;color:var(--err);font-size:16px;flex-shrink:0"><i class="fa-solid fa-triangle-exclamation"></i></div>
|
||||||
|
<div>
|
||||||
|
<div style="font-weight:700;font-size:14px">Supprimer l'agent</div>
|
||||||
|
<div style="font-size:12px;color:var(--fg2);margin-top:2px">Cette action est irréversible</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p style="font-size:13px;margin:0 0 8px">Supprimer <strong id="del-agent-name">—</strong> ?<br>
|
||||||
|
<span style="font-size:11px;color:var(--fg2)">Toutes les métriques historiques seront effacées.</span></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding:16px 20px;display:flex;justify-content:flex-end;gap:8px">
|
||||||
|
<button class="btn" onclick="document.getElementById('overlay-del').style.display='none'">Annuler</button>
|
||||||
|
<button class="btn" id="del-agent-confirm"
|
||||||
|
style="background:var(--err);color:#fff;border-color:var(--err)"
|
||||||
|
onclick="Popups.doDeleteAgent()"><i class="fa-solid fa-trash"></i> Supprimer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="js/api.js"></script>
|
<script src="js/api.js"></script>
|
||||||
<script src="js/charts.js"></script>
|
<script src="js/charts.js"></script>
|
||||||
<script src="js/grid.js"></script>
|
<script src="js/grid.js"></script>
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ const API = (() => {
|
|||||||
if (!r.ok) throw new Error(`PUT ${path}: ${r.status}`);
|
if (!r.ok) throw new Error(`PUT ${path}: ${r.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function del(path) {
|
||||||
|
const r = await fetch(BASE + path, { method: 'DELETE' });
|
||||||
|
if (!r.ok) throw new Error(`DELETE ${path}: ${r.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
async function postForm(path, formData) {
|
async function postForm(path, formData) {
|
||||||
const r = await fetch(BASE + path, { method: 'POST', body: formData });
|
const r = await fetch(BASE + path, { method: 'POST', body: formData });
|
||||||
if (!r.ok) throw new Error(`POST ${path}: ${r.status}`);
|
if (!r.ok) throw new Error(`POST ${path}: ${r.status}`);
|
||||||
@@ -44,6 +49,7 @@ const API = (() => {
|
|||||||
fd.append('icon', file);
|
fd.append('icon', file);
|
||||||
return postForm(`/api/agents/${id}/icon`, fd);
|
return postForm(`/api/agents/${id}/icon`, fd);
|
||||||
},
|
},
|
||||||
|
deleteAgent: (id) => del(`/api/agents/${id}`),
|
||||||
iconUrl: (id) => `/api/agents/${id}/icon`,
|
iconUrl: (id) => `/api/agents/${id}/icon`,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -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)}')">
|
||||||
@@ -81,19 +81,25 @@ const Grid = (() => {
|
|||||||
<div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div>
|
<div class="g-ico" data-tip="RAM"><i class="fa-solid fa-memory"></i></div>
|
||||||
<div class="g-bar"><div class="g-fill ${offline ? '' : gFill(memPct ?? 0)}"
|
<div class="g-bar"><div class="g-fill ${offline ? '' : gFill(memPct ?? 0)}"
|
||||||
style="width:${offline ? 0 : (memPct ?? 0).toFixed(0)}%"></div></div>
|
style="width:${offline ? 0 : (memPct ?? 0).toFixed(0)}%"></div></div>
|
||||||
<span class="g-val">${offline ? '—' : fmtPct(memPct)}</span>
|
<span class="g-val">${offline ? '—' : (metrics?.memory_used && metrics?.memory_total ? fmt(metrics.memory_used) + '/' + fmt(metrics.memory_total) : '—')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="g-row">
|
<div class="g-row">
|
||||||
<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">
|
||||||
${offline
|
<span class="tile-foot-info">
|
||||||
|
${offline
|
||||||
? '<i class="fa-solid fa-circle-xmark" style="color:var(--err)"></i><span style="color:var(--err)">Hors ligne</span>'
|
? '<i class="fa-solid fa-circle-xmark" style="color:var(--err)"></i><span style="color:var(--err)">Hors ligne</span>'
|
||||||
: `<i class="fa-solid fa-clock"></i><span>${uptimeStr}</span>`}
|
: `<i class="fa-solid fa-clock"></i><span>${uptimeStr || '—'}</span>`}
|
||||||
|
</span>
|
||||||
|
<button class="btn-del-agent" title="Supprimer cet agent"
|
||||||
|
onclick="event.stopPropagation();Popups.confirmDeleteAgent('${esc(id)}','${esc(agent.hostname)}')">
|
||||||
|
<i class="fa-solid fa-trash"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -101,6 +107,12 @@ const Grid = (() => {
|
|||||||
function update(agentId, metrics) {
|
function update(agentId, metrics) {
|
||||||
const entry = _agents.get(agentId);
|
const entry = _agents.get(agentId);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
|
// Conserver les valeurs lentes (disque, smart) quand le paquet ne les contient pas
|
||||||
|
if (entry.metrics) {
|
||||||
|
for (const k of Object.keys(entry.metrics)) {
|
||||||
|
if (metrics[k] == null && entry.metrics[k] != null) metrics[k] = entry.metrics[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
entry.metrics = metrics;
|
entry.metrics = metrics;
|
||||||
const el = document.getElementById('tile-' + agentId);
|
const el = document.getElementById('tile-' + agentId);
|
||||||
if (el) {
|
if (el) {
|
||||||
@@ -112,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;
|
||||||
}
|
}
|
||||||
@@ -139,6 +151,13 @@ const Grid = (() => {
|
|||||||
document.getElementById('stat-err').textContent = err;
|
document.getElementById('stat-err').textContent = err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeAgent(id) {
|
||||||
|
_agents.delete(id);
|
||||||
|
const el = document.getElementById('tile-' + id);
|
||||||
|
if (el) el.remove();
|
||||||
|
updateStats();
|
||||||
|
}
|
||||||
|
|
||||||
function getAgent(id) { return _agents.get(id); }
|
function getAgent(id) { return _agents.get(id); }
|
||||||
|
|
||||||
function updateStatus(agentId, status) {
|
function updateStatus(agentId, status) {
|
||||||
@@ -150,5 +169,5 @@ const Grid = (() => {
|
|||||||
updateStats();
|
updateStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { refresh, update, updateStatus, getAgent, fmt, fmtPct };
|
return { refresh, update, updateStatus, removeAgent, getAgent, fmt, fmtPct };
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -119,6 +138,9 @@ const Popups = (() => {
|
|||||||
<div class="meta-grid">
|
<div class="meta-grid">
|
||||||
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">${esc(agent.hostname)}</div></div>
|
<div class="meta"><div class="meta-lbl">HOSTNAME</div><div class="meta-val">${esc(agent.hostname)}</div></div>
|
||||||
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">${esc(agent.ip) || '—'}</div></div>
|
<div class="meta"><div class="meta-lbl">ADRESSE IP</div><div class="meta-val">${esc(agent.ip) || '—'}</div></div>
|
||||||
|
<div class="meta"><div class="meta-lbl">VERSION AGENT</div><div class="meta-val" style="display:flex;align-items:center;gap:6px">
|
||||||
|
${agent.version ? `<span style="font-family:var(--font-mono);background:var(--bg-1);border:1px solid var(--border-2);border-radius:5px;padding:1px 7px;font-size:11px;color:var(--accent)">v${esc(agent.version)}</span>` : '<span style="color:var(--ink-4)">—</span>'}
|
||||||
|
</div></div>
|
||||||
<div class="meta"><div class="meta-lbl">PROTOCOLES ACTIFS</div><div style="display:flex;gap:5px;margin-top:4px">${protos || '—'}</div></div>
|
<div class="meta"><div class="meta-lbl">PROTOCOLES ACTIFS</div><div style="display:flex;gap:5px;margin-top:4px">${protos || '—'}</div></div>
|
||||||
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">${new Date(agent.last_seen * 1000).toLocaleTimeString('fr-FR')}</div></div>
|
<div class="meta"><div class="meta-lbl">DERNIER CONTACT</div><div class="meta-val">${new Date(agent.last_seen * 1000).toLocaleTimeString('fr-FR')}</div></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -305,6 +327,21 @@ const Popups = (() => {
|
|||||||
${[[15,'15 min'],[30,'30 min'],[60,'1 heure'],[360,'6 heures']].map(([v,l]) =>
|
${[[15,'15 min'],[30,'30 min'],[60,'1 heure'],[360,'6 heures']].map(([v,l]) =>
|
||||||
`<option value="${v}" ${(cfg.chart_duration_min??30)==v?'selected':''}>${l}</option>`).join('')}
|
`<option value="${v}" ${(cfg.chart_duration_min??30)==v?'selected':''}>${l}</option>`).join('')}
|
||||||
</select></div>
|
</select></div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:8px">
|
||||||
|
<div class="scfg-sec-title">INSTALLATION AGENT</div>
|
||||||
|
<div class="scfg-row" style="flex-direction:column;align-items:stretch;gap:6px">
|
||||||
|
<label style="font-size:0.85em;color:var(--fg2)">Commande curl — copiez et lancez en root sur la machine cible</label>
|
||||||
|
<div style="display:flex;gap:6px;align-items:center">
|
||||||
|
<input type="text" id="s-install-cmd" readonly
|
||||||
|
style="flex:1;font-family:var(--font-mono);font-size:0.78em;padding:6px 8px;
|
||||||
|
background:var(--bg2);border:1px solid var(--border);border-radius:6px;
|
||||||
|
color:var(--fg);cursor:text;min-width:0"
|
||||||
|
value="SERVER_IP=${window.location.hostname} curl -fsSL https://git.maison43gil.com/gilles/nano_metrics/raw/branch/main/deploy/install.sh | sudo bash">
|
||||||
|
<button class="btn" style="padding:5px 10px;font-size:0.8em;white-space:nowrap"
|
||||||
|
onclick="navigator.clipboard.writeText(document.getElementById('s-install-cmd').value).then(()=>{this.textContent='✓ Copié';setTimeout(()=>this.textContent='Copier',1500)})">Copier</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
document.getElementById('overlay-srvcfg').style.display = 'flex';
|
document.getElementById('overlay-srvcfg').style.display = 'flex';
|
||||||
}
|
}
|
||||||
@@ -391,10 +428,32 @@ const Popups = (() => {
|
|||||||
document.getElementById('overlay-smart').style.display = 'flex';
|
document.getElementById('overlay-smart').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ══ SUPPRESSION AGENT ══
|
||||||
|
let _delAgentId = null;
|
||||||
|
|
||||||
|
function confirmDeleteAgent(id, hostname) {
|
||||||
|
_delAgentId = id;
|
||||||
|
document.getElementById('del-agent-name').textContent = hostname || id;
|
||||||
|
document.getElementById('overlay-del').style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doDeleteAgent() {
|
||||||
|
if (!_delAgentId) return;
|
||||||
|
const id = _delAgentId;
|
||||||
|
document.getElementById('overlay-del').style.display = 'none';
|
||||||
|
_delAgentId = null;
|
||||||
|
try {
|
||||||
|
await API.deleteAgent(id);
|
||||||
|
Grid.removeAgent(id);
|
||||||
|
} catch (e) {
|
||||||
|
alert('Erreur lors de la suppression : ' + e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
showDetail, hideDetail,
|
showDetail, hideDetail,
|
||||||
showAgentCfg, sendAgentConfig, toggleCbox,
|
showAgentCfg, sendAgentConfig, toggleCbox,
|
||||||
showSrvCfg, hideSrvCfg, saveSrvCfg,
|
showSrvCfg, hideSrvCfg, saveSrvCfg, confirmDeleteAgent, doDeleteAgent,
|
||||||
showSmart,
|
showSmart,
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -82,22 +82,23 @@ ok "Binaire téléchargé ($(du -sh "$TMP_BIN" | cut -f1))"
|
|||||||
echo ""
|
echo ""
|
||||||
echo "--- Configuration du serveur ---"
|
echo "--- Configuration du serveur ---"
|
||||||
|
|
||||||
if [ -z "${SERVER_IP:-}" ]; then
|
SERVER_IP="${SERVER_IP:-10.0.0.50}"
|
||||||
read -rp "Adresse IP du serveur Nanometrics : " SERVER_IP
|
|
||||||
fi
|
|
||||||
if [ -z "${SERVER_IP:-}" ]; then
|
|
||||||
err "SERVER_IP est requis."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
SERVER_PORT="${SERVER_PORT:-9999}"
|
SERVER_PORT="${SERVER_PORT:-9999}"
|
||||||
|
MQTT_HOST="${MQTT_HOST:-10.0.0.3}"
|
||||||
MQTT_ENABLED="${MQTT_ENABLED:-false}"
|
MQTT_ENABLED="${MQTT_ENABLED:-false}"
|
||||||
|
|
||||||
ok "Serveur : $SERVER_IP:$SERVER_PORT"
|
ok "Serveur : $SERVER_IP:$SERVER_PORT | MQTT broker : $MQTT_HOST"
|
||||||
|
|
||||||
# ── 5. Installer le binaire ────────────────────────────────────────────────────
|
# ── 5. Installer le binaire ────────────────────────────────────────────────────
|
||||||
echo ""
|
echo ""
|
||||||
echo "[1/5] Installation du binaire dans /usr/local/bin/"
|
echo "[1/5] Installation du binaire dans /usr/local/bin/"
|
||||||
|
|
||||||
|
# Arrêter le service si en cours (le binaire ne peut pas être écrasé à chaud)
|
||||||
|
if systemctl is-active --quiet nanometrics-agent 2>/dev/null; then
|
||||||
|
warn "Service en cours — arrêt temporaire..."
|
||||||
|
systemctl stop nanometrics-agent
|
||||||
|
fi
|
||||||
|
|
||||||
cp "$TMP_BIN" "$INSTALL_BIN"
|
cp "$TMP_BIN" "$INSTALL_BIN"
|
||||||
chmod 755 "$INSTALL_BIN"
|
chmod 755 "$INSTALL_BIN"
|
||||||
ok "Binaire installé"
|
ok "Binaire installé"
|
||||||
@@ -111,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"
|
||||||
@@ -125,7 +149,7 @@ enabled = true
|
|||||||
|
|
||||||
[protocols.mqtt]
|
[protocols.mqtt]
|
||||||
enabled = $MQTT_ENABLED
|
enabled = $MQTT_ENABLED
|
||||||
host = "10.0.0.3"
|
host = "$MQTT_HOST"
|
||||||
port = 1883
|
port = 1883
|
||||||
topic_base = "nanometrics/agents"
|
topic_base = "nanometrics/agents"
|
||||||
auto_discovery = true
|
auto_discovery = true
|
||||||
@@ -160,10 +184,14 @@ mqtt = false
|
|||||||
udp = true
|
udp = true
|
||||||
mqtt = false
|
mqtt = false
|
||||||
TOML
|
TOML
|
||||||
chmod 640 "$CONFIG_FILE"
|
chmod 644 "$CONFIG_FILE"
|
||||||
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
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -40,7 +42,9 @@ for i in "${!TARGETS[@]}"; do
|
|||||||
# Installer la cible si absente
|
# Installer la cible si absente
|
||||||
rustup target add "$TARGET" 2>/dev/null || true
|
rustup target add "$TARGET" 2>/dev/null || true
|
||||||
|
|
||||||
# Compiler
|
# Compiler (CC requis par la crate ring pour les cibles musl cross)
|
||||||
|
CC_aarch64_unknown_linux_musl=aarch64-linux-gnu-gcc \
|
||||||
|
AR_aarch64_unknown_linux_musl=aarch64-linux-gnu-ar \
|
||||||
cargo build --release \
|
cargo build --release \
|
||||||
--manifest-path "$CARGO_TOML" \
|
--manifest-path "$CARGO_TOML" \
|
||||||
--target "$TARGET" \
|
--target "$TARGET" \
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const schema = `
|
|||||||
CREATE TABLE IF NOT EXISTS agents (
|
CREATE TABLE IF NOT EXISTS agents (
|
||||||
id TEXT PRIMARY KEY, hostname TEXT NOT NULL,
|
id TEXT PRIMARY KEY, hostname TEXT NOT NULL,
|
||||||
ip TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'offline',
|
ip TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'offline',
|
||||||
last_seen INTEGER NOT NULL DEFAULT 0
|
last_seen INTEGER NOT NULL DEFAULT 0, version TEXT NOT NULL DEFAULT ''
|
||||||
);
|
);
|
||||||
CREATE TABLE IF NOT EXISTS metrics (
|
CREATE TABLE IF NOT EXISTS metrics (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id TEXT NOT NULL, ts INTEGER NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT, agent_id TEXT NOT NULL, ts INTEGER NOT NULL,
|
||||||
@@ -57,8 +57,12 @@ func Open(path string) (*DB, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DB) migrate() error {
|
func (d *DB) migrate() error {
|
||||||
_, err := d.conn.Exec(schema)
|
if _, err := d.conn.Exec(schema); err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
// Migrations additives pour les colonnes ajoutées après la création initiale
|
||||||
|
_, _ = d.conn.Exec(`ALTER TABLE agents ADD COLUMN version TEXT NOT NULL DEFAULT ''`)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DB) Close() { _ = d.conn.Close() }
|
func (d *DB) Close() { _ = d.conn.Close() }
|
||||||
@@ -66,11 +70,12 @@ func (d *DB) Close() { _ = d.conn.Close() }
|
|||||||
func (d *DB) UpsertAgent(m *models.AgentMetrics) error {
|
func (d *DB) UpsertAgent(m *models.AgentMetrics) error {
|
||||||
ts := time.Now().Unix()
|
ts := time.Now().Unix()
|
||||||
_, err := d.conn.Exec(`
|
_, err := d.conn.Exec(`
|
||||||
INSERT INTO agents (id, hostname, ip, status, last_seen)
|
INSERT INTO agents (id, hostname, ip, status, last_seen, version)
|
||||||
VALUES (?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
ON CONFLICT(id) DO UPDATE SET
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
ip=excluded.ip, status=excluded.status, last_seen=excluded.last_seen`,
|
ip=excluded.ip, status=excluded.status, last_seen=excluded.last_seen,
|
||||||
m.Hostname, m.Hostname, m.IP, m.Status, ts)
|
version=CASE WHEN excluded.version != '' THEN excluded.version ELSE version END`,
|
||||||
|
m.Hostname, m.Hostname, m.IP, m.Status, ts, m.Version)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +109,7 @@ func (d *DB) InsertMetrics(m *models.AgentMetrics) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DB) GetAgents() ([]models.Agent, error) {
|
func (d *DB) GetAgents() ([]models.Agent, error) {
|
||||||
rows, err := d.conn.Query(`SELECT id, hostname, ip, status, last_seen FROM agents`)
|
rows, err := d.conn.Query(`SELECT id, hostname, ip, status, last_seen, version FROM agents`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -112,7 +117,7 @@ func (d *DB) GetAgents() ([]models.Agent, error) {
|
|||||||
var agents []models.Agent
|
var agents []models.Agent
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var a models.Agent
|
var a models.Agent
|
||||||
if err := rows.Scan(&a.ID, &a.Hostname, &a.IP, &a.Status, &a.LastSeen); err != nil {
|
if err := rows.Scan(&a.ID, &a.Hostname, &a.IP, &a.Status, &a.LastSeen, &a.Version); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
agents = append(agents, a)
|
agents = append(agents, a)
|
||||||
@@ -123,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
|
||||||
@@ -238,6 +309,20 @@ func (d *DB) MarkOffline(timeoutSec int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DB) DeleteAgent(agentID string) error {
|
||||||
|
for _, q := range []string{
|
||||||
|
`DELETE FROM metrics WHERE agent_id = ?`,
|
||||||
|
`DELETE FROM agent_configs WHERE agent_id = ?`,
|
||||||
|
`DELETE FROM agent_icons WHERE agent_id = ?`,
|
||||||
|
`DELETE FROM agents WHERE id = ?`,
|
||||||
|
} {
|
||||||
|
if _, err := d.conn.Exec(q, agentID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// MarkOfflineAndGetIDs marque les agents inactifs et retourne leurs IDs.
|
// MarkOfflineAndGetIDs marque les agents inactifs et retourne leurs IDs.
|
||||||
func (d *DB) MarkOfflineAndGetIDs(timeoutSec int64) ([]string, error) {
|
func (d *DB) MarkOfflineAndGetIDs(timeoutSec int64) ([]string, error) {
|
||||||
cutoff := time.Now().Unix() - timeoutSec
|
cutoff := time.Now().Unix() - timeoutSec
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package handlers
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/user/nanometrics/server/db"
|
"github.com/user/nanometrics/server/db"
|
||||||
)
|
)
|
||||||
@@ -14,7 +15,26 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DeleteAgentHandler(database *db.DB) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
||||||
|
if len(parts) < 3 {
|
||||||
|
http.Error(w, "invalid path", 400)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
agentID := parts[2]
|
||||||
|
if err := database.DeleteAgent(agentID); err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func AgentConfigHandler(database *db.DB, pushConfig func(agentID string, cfg *mo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
cfg = &models.AgentConfig{}
|
cfg = models.DefaultAgentConfig()
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(cfg)
|
json.NewEncoder(w).Encode(cfg)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -110,6 +110,8 @@ func main() {
|
|||||||
handlers.IconUploadHandler(database)(w, r)
|
handlers.IconUploadHandler(database)(w, r)
|
||||||
case endsWith(r.URL.Path, "/icon") && r.Method == http.MethodGet:
|
case endsWith(r.URL.Path, "/icon") && r.Method == http.MethodGet:
|
||||||
handlers.IconGetHandler(database)(w, r)
|
handlers.IconGetHandler(database)(w, r)
|
||||||
|
case r.Method == http.MethodDelete:
|
||||||
|
handlers.DeleteAgentHandler(database)(w, r)
|
||||||
default:
|
default:
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ type AgentMetrics struct {
|
|||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"`
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
Version string `json:"version"`
|
||||||
CPUPercent *float64 `json:"cpu_percent"`
|
CPUPercent *float64 `json:"cpu_percent"`
|
||||||
MemoryUsed *int64 `json:"memory_used"`
|
MemoryUsed *int64 `json:"memory_used"`
|
||||||
MemoryFree *int64 `json:"memory_free"`
|
MemoryFree *int64 `json:"memory_free"`
|
||||||
@@ -32,6 +33,7 @@ type Agent struct {
|
|||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
LastSeen int64 `json:"last_seen"`
|
LastSeen int64 `json:"last_seen"`
|
||||||
|
Version string `json:"version,omitempty"`
|
||||||
LastMetrics *AgentMetrics `json:"last_metrics,omitempty"`
|
LastMetrics *AgentMetrics `json:"last_metrics,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +90,20 @@ type ServerConfig struct {
|
|||||||
PopupDetailH int `json:"popup_detail_h"`
|
PopupDetailH int `json:"popup_detail_h"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func DefaultAgentConfig() *AgentConfig {
|
||||||
|
on := MetricProto{UDP: true, MQTT: false}
|
||||||
|
return &AgentConfig{
|
||||||
|
Protocols: ProtocolsConfig{UDP: UDPConfig{Enabled: true}},
|
||||||
|
Metrics: MetricsConfig{
|
||||||
|
CPU: on,
|
||||||
|
Memory: on,
|
||||||
|
Disk: on,
|
||||||
|
Smart: on,
|
||||||
|
Uptime: on,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func DefaultServerConfig() ServerConfig {
|
func DefaultServerConfig() ServerConfig {
|
||||||
return ServerConfig{
|
return ServerConfig{
|
||||||
TileMinWidth: 220, FontSize: 13,
|
TileMinWidth: 220, FontSize: 13,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||