feat(rust): advance powershell migration

This commit is contained in:
igor04091968
2026-06-06 08:58:46 +03:00
parent 2ec1836e8c
commit 9c57d6d2ce
42 changed files with 10056 additions and 407 deletions
+399 -18
View File
@@ -164,6 +164,26 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aw-1c-ingest"
version = "0.1.0"
dependencies = [
"anyhow",
"calamine",
"chrono",
"clap",
"csv",
"filetime",
"fs2",
"regex",
"reqwest",
"serde",
"serde_json",
"serde_yaml",
"sha1",
"tempfile",
]
[[package]]
name = "aw-browser-smoke"
version = "0.1.0"
@@ -283,6 +303,27 @@ dependencies = [
"urlencoding",
]
[[package]]
name = "aw-windows-telemetry"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"chrono",
"clap",
"csv",
"notify",
"regex",
"reqwest",
"serde",
"serde_json",
"sha2",
"tempfile",
"url",
"windows",
"windows-sys 0.59.0",
]
[[package]]
name = "awatch-agent-rs"
version = "0.3.0"
@@ -303,6 +344,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.1"
@@ -324,12 +371,33 @@ version = "3.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "calamine"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a3a315226fdc5b1c3e33521073e1712a05944bc0664d665ff1f6ff0396334da"
dependencies = [
"byteorder",
"codepage",
"encoding_rs",
"log",
"quick-xml",
"serde",
"zip 0.6.6",
]
[[package]]
name = "cc"
version = "1.2.63"
@@ -399,7 +467,7 @@ dependencies = [
"num-traits",
"serde",
"wasm-bindgen",
"windows-link",
"windows-link 0.2.1",
]
[[package]]
@@ -448,6 +516,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "codepage"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f68d061bc2828ae826206326e61251aca94c1e4a5305cf52d9138639c918b4"
dependencies = [
"encoding_rs",
]
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -478,6 +555,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
@@ -494,6 +580,27 @@ dependencies = [
"typenum",
]
[[package]]
name = "csv"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde_core",
]
[[package]]
name = "csv-core"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782"
dependencies = [
"memchr",
]
[[package]]
name = "derive_arbitrary"
version = "1.4.2"
@@ -819,6 +926,15 @@ dependencies = [
"tempfile",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -917,6 +1033,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
dependencies = [
"libc",
]
[[package]]
name = "futures"
version = "0.3.32"
@@ -1226,7 +1351,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
"windows-core 0.62.2",
]
[[package]]
@@ -1358,6 +1483,26 @@ dependencies = [
"serde_core",
]
[[package]]
name = "inotify"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff"
dependencies = [
"bitflags 1.3.2",
"inotify-sys",
"libc",
]
[[package]]
name = "inotify-sys"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
dependencies = [
"libc",
]
[[package]]
name = "ipnet"
version = "2.12.0"
@@ -1388,6 +1533,26 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kqueue"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "273c0752728918e0ac4976f2b275b6fefb9ecd400585dec929419f3844cd87b5"
dependencies = [
"kqueue-sys",
"libc",
]
[[package]]
name = "kqueue-sys"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087"
dependencies = [
"bitflags 2.11.1",
"libc",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
@@ -1461,6 +1626,18 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "mio"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.2.1"
@@ -1472,6 +1649,25 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "notify"
version = "6.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d"
dependencies = [
"bitflags 2.11.1",
"crossbeam-channel",
"filetime",
"fsevent-sys",
"inotify",
"kqueue",
"libc",
"log",
"mio 0.8.11",
"walkdir",
"windows-sys 0.48.0",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@@ -1581,6 +1777,16 @@ dependencies = [
"tempfile",
]
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"encoding_rs",
"memchr",
]
[[package]]
name = "quinn"
version = "0.11.9"
@@ -1709,7 +1915,7 @@ dependencies = [
"sha2",
"tar",
"tempfile",
"zip",
"zip 2.4.2",
]
[[package]]
@@ -1801,7 +2007,7 @@ version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e"
dependencies = [
"bitflags",
"bitflags 2.11.1",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
@@ -1821,7 +2027,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"bitflags 2.11.1",
"errno",
"libc",
"linux-raw-sys",
@@ -1875,6 +2081,15 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "semver"
version = "1.0.28"
@@ -1949,6 +2164,17 @@ dependencies = [
"unsafe-libyaml",
]
[[package]]
name = "sha1"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "sha2"
version = "0.10.9"
@@ -2142,7 +2368,7 @@ checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe"
dependencies = [
"bytes",
"libc",
"mio",
"mio 1.2.1",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@@ -2192,7 +2418,7 @@ version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
dependencies = [
"bitflags",
"bitflags 2.11.1",
"bytes",
"futures-util",
"http",
@@ -2362,7 +2588,7 @@ dependencies = [
"sha2",
"tar",
"tempfile",
"zip",
"zip 2.4.2",
]
[[package]]
@@ -2388,6 +2614,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
@@ -2504,7 +2740,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"bitflags 2.11.1",
"hashbrown 0.15.5",
"indexmap",
"semver",
@@ -2555,23 +2791,66 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1"
dependencies = [
"windows-core 0.59.0",
"windows-targets 0.53.5",
]
[[package]]
name = "windows-core"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce"
dependencies = [
"windows-implement 0.59.0",
"windows-interface",
"windows-result 0.3.4",
"windows-strings 0.3.1",
"windows-targets 0.53.5",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-implement 0.60.2",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-implement"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
@@ -2596,19 +2875,43 @@ dependencies = [
"syn",
]
[[package]]
name = "windows-link"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
@@ -2617,7 +2920,16 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
@@ -2653,7 +2965,22 @@ version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
"windows-link 0.2.1",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
@@ -2678,7 +3005,7 @@ version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows-link 0.2.1",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
@@ -2689,6 +3016,12 @@ dependencies = [
"windows_x86_64_msvc 0.53.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
@@ -2701,6 +3034,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
@@ -2713,6 +3052,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@@ -2737,6 +3082,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
@@ -2749,6 +3100,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
@@ -2761,6 +3118,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
@@ -2773,6 +3136,12 @@ version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
@@ -2849,7 +3218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"bitflags 2.11.1",
"indexmap",
"log",
"serde",
@@ -3064,6 +3433,18 @@ dependencies = [
"syn",
]
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
"flate2",
]
[[package]]
name = "zip"
version = "2.4.2"
+8
View File
@@ -1,6 +1,8 @@
[workspace]
resolver = "3"
members = [
"crates/aw-1c-ingest",
"crates/aw-windows-telemetry",
"crates/awatch-agent-rs",
"crates/detmir-auto",
"crates/detmir-aw-client",
@@ -67,20 +69,26 @@ publish = false
adk-rust = { version = "0.9.1", default-features = false }
anyhow = "1"
base64 = "0.22"
calamine = "=0.24.0"
chrono = { version = "0.4", default-features = false, features = ["clock", "serde", "std"] }
clap = { version = "4", features = ["derive", "env"] }
csv = "1"
detmir-aw-client = { path = "crates/detmir-aw-client" }
detmir-core = { path = "crates/detmir-core" }
detmir-state = { path = "crates/detmir-state" }
filetime = "0.2"
fs2 = "0.4"
notify = "6"
reqwest = { version = "0.12", default-features = false, features = ["blocking", "json", "rustls-tls"] }
regex = "1"
rusqlite = "0.32"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
serde_yaml = "0.9"
sha1 = "0.10"
sha2 = "0.10"
tempfile = "3"
tiny_http = "0.12"
url = "2"
urlencoding = "2"
windows-sys = "0.59"
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "aw-1c-ingest"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish = false
[dependencies]
anyhow.workspace = true
calamine.workspace = true
chrono.workspace = true
clap.workspace = true
csv.workspace = true
filetime.workspace = true
fs2.workspace = true
regex.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
serde_yaml.workspace = true
sha1.workspace = true
tempfile.workspace = true
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
[package]
name = "aw-windows-telemetry"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
publish = false
[dependencies]
anyhow.workspace = true
base64.workspace = true
chrono.workspace = true
clap.workspace = true
csv.workspace = true
notify.workspace = true
reqwest.workspace = true
regex.workspace = true
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
tempfile.workspace = true
url.workspace = true
[target.'cfg(windows)'.dependencies]
windows = { version = "0.59", features = ["Win32_Foundation", "Win32_System_Com", "Win32_UI_Accessibility", "Win32_UI_WindowsAndMessaging"] }
windows-sys = { workspace = true, features = ["Win32_Foundation", "Win32_Storage_FileSystem", "Win32_System_DataExchange", "Win32_System_Diagnostics_Debug", "Win32_System_Diagnostics_ToolHelp", "Win32_System_Memory", "Win32_System_RemoteDesktop", "Win32_System_SystemInformation", "Win32_System_Threading", "Win32_UI_WindowsAndMessaging"] }
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -5,7 +5,7 @@ use anyhow::{Context, Result};
use clap::Parser;
const DEFAULT_SSH_TARGET: &str = "igor@192.0.2.13";
const DEFAULT_REMOTE_COMMAND: &str = "sudo -n /usr/local/bin/dlp-health-check --json";
const DEFAULT_REMOTE_COMMAND: &str = "sudo -n env AW_DLP_HEALTH_ENDPOINT_SEND_FAILURE_WARN_COUNT=10 AW_DLP_HEALTH_FILEOPS_SEND_FAILURE_WARN_COUNT=10 /usr/local/bin/dlp-health-check --json";
#[derive(Debug, Parser)]
#[command(about = "Run the DetMir DLP health check on the AW server over SSH.")]
+129 -16
View File
@@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
#[cfg(unix)]
use std::os::unix::process::CommandExt;
@@ -29,7 +29,7 @@ const APP_JS: &str = include_str!("static/app.js");
const API_CONTRACT_OPENAPI: &str = include_str!("contracts/openapi.json");
const API_CONTRACT_TYPESCRIPT: &str = include_str!("contracts/typescript.d.ts");
const UEBA_BASELINE_MIN_SAMPLES: usize = 3;
const SNAPSHOT_CACHE_TTL: Duration = Duration::from_secs(5);
const SNAPSHOT_CACHE_TTL: Duration = Duration::from_secs(120);
const DEFAULT_DEPARTMENT_LABEL: &str = "Без подразделения";
#[cfg(unix)]
@@ -1308,10 +1308,7 @@ fn handle_request(request: Request, args: &Cli, snapshot_cache: &SnapshotCache)
API_CONTRACT_TYPESCRIPT,
"text/plain; charset=utf-8",
),
"/api/health" => respond_json(
request,
&build_health(&cached_snapshot(args, snapshot_cache)),
),
"/api/health" => respond_json(request, &build_fast_health(snapshot_cache)),
"/api/readiness/latest" => respond_json(request, &readiness_latest(args)),
"/api/readiness/bundle" => respond_json(request, &readiness_bundle(args)),
"/api/readiness/verify" => respond_json(request, &readiness_verify(args)),
@@ -1642,6 +1639,27 @@ fn cached_snapshot(args: &Cli, cache: &SnapshotCache) -> Snapshot {
snapshot
}
fn build_fast_health(cache: &SnapshotCache) -> HealthResponse {
match cache.try_lock() {
Ok(guard) => guard
.as_ref()
.map(|cached| build_health(&cached.snapshot))
.unwrap_or_else(lightweight_health),
Err(_) => lightweight_health(),
}
}
fn lightweight_health() -> HealthResponse {
let mut sources = BTreeMap::new();
sources.insert("portal".to_string(), true);
HealthResponse {
ok: true,
generated_at_utc: now(),
version: env!("CARGO_PKG_VERSION").to_string(),
sources,
}
}
fn build_snapshot(args: &Cli) -> Snapshot {
let timeout = Duration::from_secs(args.timeout_seconds);
let security_events_config = SecurityEventsConfig {
@@ -2630,12 +2648,18 @@ fn security_events_block(summary: &SecurityEventsSummary) -> SummaryBlock {
}
fn run_shell(command: &str, timeout: Duration) -> Result<(String, String, bool)> {
let stdout_path = command_output_path("stdout");
let stderr_path = command_output_path("stderr");
let stdout_file =
File::create(&stdout_path).with_context(|| format!("create {}", stdout_path.display()))?;
let stderr_file =
File::create(&stderr_path).with_context(|| format!("create {}", stderr_path.display()))?;
let mut shell = Command::new("/bin/sh");
shell
.arg("-lc")
.arg(command)
.stdout(Stdio::piped())
.stderr(Stdio::piped());
.stdout(Stdio::from(stdout_file))
.stderr(Stdio::from(stderr_file));
#[cfg(unix)]
unsafe {
shell.pre_exec(|| {
@@ -2652,19 +2676,15 @@ fn run_shell(command: &str, timeout: Duration) -> Result<(String, String, bool)>
let started = Instant::now();
loop {
if let Some(status) = child.try_wait()? {
let mut stdout = String::new();
let mut stderr = String::new();
if let Some(mut pipe) = child.stdout.take() {
pipe.read_to_string(&mut stdout)?;
}
if let Some(mut pipe) = child.stderr.take() {
pipe.read_to_string(&mut stderr)?;
}
let stdout = read_command_output(&stdout_path)?;
let stderr = read_command_output(&stderr_path)?;
cleanup_command_output(&stdout_path, &stderr_path);
return Ok((stdout, stderr, status.success()));
}
if started.elapsed() > timeout {
kill_shell_tree(&mut child);
let _ = child.wait();
cleanup_command_output(&stdout_path, &stderr_path);
return Err(anyhow!(
"command timed out after {}s: {command}",
timeout.as_secs()
@@ -2674,6 +2694,27 @@ fn run_shell(command: &str, timeout: Duration) -> Result<(String, String, bool)>
}
}
fn command_output_path(stream: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
std::env::temp_dir().join(format!(
"detmir-portal-command-{}-{nanos}-{stream}.log",
std::process::id()
))
}
fn read_command_output(path: &Path) -> Result<String> {
let bytes = fs::read(path).with_context(|| format!("read {}", path.display()))?;
Ok(String::from_utf8_lossy(&bytes).into_owned())
}
fn cleanup_command_output(stdout_path: &Path, stderr_path: &Path) {
let _ = fs::remove_file(stdout_path);
let _ = fs::remove_file(stderr_path);
}
#[cfg(unix)]
fn kill_shell_tree(child: &mut std::process::Child) {
let pgid = child.id() as i32;
@@ -3519,6 +3560,9 @@ fn build_risk_incident_candidates(
candidates.extend(low_trust_risk_candidates(risks, snapshot));
candidates.extend(agent_quality_candidates(snapshot, &problem_nodes_by_host));
candidates.extend(agent_coverage_candidates(snapshot));
if candidates.is_empty() {
candidates.extend(workforce_insight_risk_candidates(snapshot, risks));
}
candidates.sort_by(|left, right| {
risk_incident_candidate_rank(right)
@@ -4720,6 +4764,64 @@ fn agent_coverage_candidates(snapshot: &Snapshot) -> Vec<RiskIncidentCandidate>
.collect()
}
fn workforce_insight_risk_candidates(
snapshot: &Snapshot,
risks: &[BusinessRiskItem],
) -> Vec<RiskIncidentCandidate> {
let primary_risk = risks.first();
let department = primary_risk
.map(|item| item.department.clone())
.unwrap_or_else(|| DEFAULT_DEPARTMENT_LABEL.to_string());
let risk_level = primary_risk
.map(|item| item.risk_level.clone())
.unwrap_or_else(|| "MEDIUM".to_string());
let recommendation = primary_risk
.map(|item| item.recommendation.clone())
.unwrap_or_else(|| {
"Проверить первичные события ActivityWatch и причины отклонения активности.".to_string()
});
workforce_insight_items(snapshot)
.into_iter()
.filter(|item| {
let status = item.get("status").and_then(Value::as_str).unwrap_or("INFO");
!matches!(status, "OK" | "INFO")
})
.take(3)
.map(|item| {
let label = item
.get("label")
.and_then(Value::as_str)
.unwrap_or("Отклонение активности");
let value = item.get("value").and_then(Value::as_str).unwrap_or("");
RiskIncidentCandidate {
id: risk_candidate_id(
"workforce-insight",
&department,
&format!("{label}:{value}"),
),
department: Some(department.clone()),
owner: None,
hostname: None,
risk_level: Some(risk_level.clone()),
reason: Some(label.to_string()),
evidence: vec![
format!("activity_signal={label}"),
format!("details={value}"),
format!(
"security_events_24h={}",
snapshot.security_events_summary.events_24h
),
],
first_seen_utc: Some(snapshot.generated_at_utc.clone()),
last_seen_utc: Some(snapshot.generated_at_utc.clone()),
recommendation: Some(recommendation.clone()),
incident_review: IncidentReviewState::default(),
incident_review_audit: Vec::new(),
}
})
.collect()
}
fn agent_quality_candidate_level(node: &AgentQualityNodeItem) -> String {
match node.status.as_str() {
"DEGRADED" => "HIGH",
@@ -9458,6 +9560,17 @@ mod tests {
assert!(err.to_string().contains("command timed out"));
}
#[cfg(unix)]
#[test]
fn run_shell_does_not_block_on_background_stdout_handle() {
let started = Instant::now();
let (stdout, _, success) =
run_shell("sh -c 'sleep 5 & printf ok'", Duration::from_secs(2)).unwrap();
assert!(started.elapsed() < Duration::from_secs(2));
assert!(success);
assert_eq!(stdout, "ok");
}
#[test]
fn command_json_source_accepts_valid_json_with_nonzero_exit() {
let source = command_json_source(
+61 -42
View File
@@ -123,6 +123,10 @@ function displayText(value) {
.replaceAll("Grafana dashboards", "графики")
.replaceAll("Grafana data", "данные графиков")
.replaceAll("Grafana", "Графики")
.replaceAll("SECURITY_EVENTS_BACKEND", "настройка источника событий")
.replaceAll("CLICKHOUSE_*", "параметры источника событий")
.replaceAll("ClickHouse", "источник событий")
.replaceAll("local_fallback", "резервный локальный источник")
.replaceAll("DetMir ActivityWatch", "Журнал активности")
.replaceAll("AW UI", "Журнал активности")
.replaceAll("bundle", "пакет проверки")
@@ -166,9 +170,20 @@ function displayText(value) {
.replaceAll("daily", "день")
.replaceAll("weekly", "неделя")
.replaceAll("monthly", "месяц")
.replaceAll("OK", "OK")
.replaceAll("FAIL", "FAIL")
.replaceAll("WARN", "WARN");
.replace(/\bCRITICAL\b/g, "критично")
.replace(/\bWARNING\b/g, "требует внимания")
.replace(/\bUNKNOWN\b/g, "нет данных")
.replace(/\bDISABLED\b/g, "отключено")
.replace(/\bREADY\b/g, "готово")
.replace(/\bOPEN\b/g, "открыто")
.replace(/\bINFO\b/g, "информация")
.replace(/\bHIGH\b/g, "высокий риск")
.replace(/\bMEDIUM\b/g, "средний риск")
.replace(/\bLOW\b/g, "низкий риск")
.replace(/\bFAIL\b/g, "критично")
.replace(/\bWARN\b/g, "требует внимания")
.replace(/\bNO\b/g, "нет")
.replace(/\bOK\b/g, "в норме");
}
function ui(value) {
@@ -233,7 +248,7 @@ function setViewMode(mode) {
function renderSummary(summary, readiness) {
const global = document.getElementById("globalStatus");
global.className = `status-pill ${statusClass(summary.severity)}`;
global.textContent = `Сбор данных ${summary.operator_ok ? "OK" : "NO"} · ${summary.severity}`;
global.textContent = `Сбор данных ${summary.operator_ok ? "в норме" : "нет данных"} · ${displayText(summary.severity)}`;
const blocks = Object.entries(summary.blocks || {});
const readinessCard = renderReadinessSummaryCard(readiness);
document.getElementById("summary").innerHTML = readinessCard + blocks.map(([name, block]) => `
@@ -268,8 +283,8 @@ function renderReadinessSummaryCard(readiness) {
</div>
<div class="readiness-metrics">
<div><span class="muted">Дата</span><strong>${escapeHtml(generated)}</strong></div>
<div><span class="muted">Подпись</span><strong class="${signatureOk ? "text-ok" : "text-fail"}">${signatureOk ? "OK" : "FAIL"}</strong></div>
<div><span class="muted">Контрольная сумма</span><strong class="${checksumOk ? "text-ok" : "text-fail"}">${checksumOk ? "OK" : "FAIL"}</strong></div>
<div><span class="muted">Подпись</span><strong class="${signatureOk ? "text-ok" : "text-fail"}">${signatureOk ? "подтверждена" : "не подтверждена"}</strong></div>
<div><span class="muted">Контрольная сумма</span><strong class="${checksumOk ? "text-ok" : "text-fail"}">${checksumOk ? "подтверждена" : "не подтверждена"}</strong></div>
</div>
<p class="muted small">Отпечаток ключа: <code>${escapeHtml(shortFingerprint(fingerprint))}</code></p>
<p id="readinessVerifyStatus" class="muted small">${escapeHtml(verificationText)}</p>
@@ -774,7 +789,7 @@ function departmentRows(report) {
total: parsed.total,
hhmm: parsed.hhmm,
trend,
risk: status === "FAIL" ? "FAIL — требуется действие" : status === "WARN" ? "WARN — требуется внимание" : "LOW — все нормально",
risk: status === "FAIL" ? "критично — требуется действие" : status === "WARN" ? "требует внимания" : "низкий риск — все нормально",
reason: departmentRiskReason({ item, parsed, activity, status, trend }),
check: departmentCheckText({ parsed, status, trend }),
};
@@ -808,8 +823,8 @@ function executiveDashboardKpis(report) {
return [
metricCard("Сотрудников в работе", latest.active_users ?? findKpi(report, "Сотрудники")?.value ?? "нет данных", "OK", "активны сегодня"),
metricCard("Средний индекс активности", Number.isFinite(average) ? `${average}%` : findKpi(report, "Индекс активности")?.value, average === null ? "UNKNOWN" : workforceIndexStatus(average), "по подразделениям"),
metricCard("WARN подразделений", rows.filter(row => row.status === "WARN").length, rows.some(row => row.status === "WARN") ? "WARN" : "OK", "требуется внимание"),
metricCard("FAIL подразделений", rows.filter(row => row.status === "FAIL").length, rows.some(row => row.status === "FAIL") ? "FAIL" : "OK", "требуется действие"),
metricCard("Требуют внимания", rows.filter(row => row.status === "WARN").length, rows.some(row => row.status === "WARN") ? "WARN" : "OK", "подразделения"),
metricCard("Критичные подразделения", rows.filter(row => row.status === "FAIL").length, rows.some(row => row.status === "FAIL") ? "FAIL" : "OK", "требуется действие"),
metricCard("Критических рисков", criticalRisks, criticalRisks ? "FAIL" : "OK", "приоритетный разбор"),
metricCard("Тренд недели", signedPercent(weeklyTrendPct(report)), Number(weeklyTrendPct(report)) < 0 ? "WARN" : "OK", "динамика активности"),
].join("");
@@ -906,7 +921,7 @@ function renderDepartmentRanking(report) {
<span class="badge ${statusClass(row.status)}">${ui(row.status)}</span>
</div>
`).join("")}</div>`;
const emptyRows = `<div class="list compact-list"><div class="row compact-row"><strong>Нет данных</strong><span class="muted">Подразделения пока не рассчитаны.</span><span class="badge status-unknown">UNKNOWN</span></div></div>`;
const emptyRows = `<div class="list compact-list"><div class="row compact-row"><strong>Нет данных</strong><span class="muted">Подразделения пока не рассчитаны.</span><span class="badge status-unknown">нет данных</span></div></div>`;
return `
<section class="ranking-grid">
<article class="card">
@@ -994,7 +1009,7 @@ function renderDepartmentHeatMap(report) {
<td><strong>Нет данных</strong><small>Подразделения пока не рассчитаны.</small></td>
<td>-</td>
<td>-</td>
<td><span class="badge status-unknown">UNKNOWN</span></td>
<td><span class="badge status-unknown">нет данных</span></td>
<td>-</td>
<td><button class="small-button" data-open-investigation="true">Открыть расследование</button></td>
</tr>
@@ -1149,7 +1164,7 @@ function renderSourceList(data) {
<div class="row">
<strong>${ui(name)}</strong>
<span class="muted">${ui(source?.summary || source?.error || "нет данных")}</span>
<span class="badge ${statusClass(source?.status || source?.ok)}">${escapeHtml(source?.status || (source?.ok ? "OK" : "FAIL"))}</span>
<span class="badge ${statusClass(source?.status || source?.ok)}">${ui(source?.status || (source?.ok ? "OK" : "FAIL"))}</span>
</div>
`).join("")}</div>`;
}
@@ -1243,7 +1258,7 @@ function renderOperationsErrors(data, report) {
if (report?.security_events_summary?.fallback_used) {
rows.push([
"События безопасности",
report.security_events_summary.error || "ClickHouse недоступен",
report.security_events_summary.error || "Источник событий недоступен",
"WARN",
]);
}
@@ -1282,7 +1297,7 @@ function renderOperationsErrors(data, report) {
<div class="row compact-row">
<strong>Критичных ошибок нет</strong>
<span class="muted">Портал не видит ошибок коллектора или проблемных рабочих мест в текущем срезе.</span>
<span class="badge status-ok">OK</span>
<span class="badge status-ok">в норме</span>
</div>
`}</div>
</section>
@@ -1335,7 +1350,7 @@ function renderInvestigationPacks(candidates) {
<div class="row compact-row">
<strong>Пакетов нет</strong>
<span class="muted">Нет записей, требующих выгрузки материалов.</span>
<span class="badge status-ok">OK</span>
<span class="badge status-ok">в норме</span>
</div>
`}</div>
</section>
@@ -1855,7 +1870,7 @@ function renderCases(cases) {
<tr>
<td>Нет дел</td>
<td>-</td>
<td><span class="badge status-ok">OK</span></td>
<td><span class="badge status-ok">в норме</span></td>
<td>-</td>
<td>Создайте дело из подтвержденного кандидата.</td>
<td>-</td>
@@ -1941,7 +1956,7 @@ function renderUebaRisk(risk) {
<span class="muted">${ui(item.value || "")} · ${ui(item.recommendation || "")}</span>
<span class="badge ${statusClass(item.status || item.severity)}">+${escapeHtml(item.points || 0)}</span>
</div>
`).join("") : `<div class="row compact-row"><strong>Сигналы</strong><span class="muted">Существенных риск-сигналов в текущем срезе нет.</span><span class="badge status-ok">OK</span></div>`}</div>
`).join("") : `<div class="row compact-row"><strong>Сигналы</strong><span class="muted">Существенных риск-сигналов в текущем срезе нет.</span><span class="badge status-ok">в норме</span></div>`}</div>
</section>
`;
}
@@ -2038,7 +2053,7 @@ function renderAgentQualityHistory(history, summary) {
<span class="badge ${statusClass(status)}">${ui(status)}</span>
</div>
<div class="quality-decision">
<div><span class="muted">OK дней</span><strong>${escapeHtml(s.ok_days ?? 0)}</strong></div>
<div><span class="muted">Дней в норме</span><strong>${escapeHtml(s.ok_days ?? 0)}</strong></div>
<div><span class="muted">Проблемных дней</span><strong>${escapeHtml(unstableDays)}</strong></div>
<div><span class="muted">Показатели подтверждены</span><strong>${escapeHtml(s.kpi_accepted_pct ?? 0)}%</strong></div>
</div>
@@ -2051,7 +2066,7 @@ function renderAgentQualityHistory(history, summary) {
<span class="muted">источник=${ui(item.source || "unknown")} · показатели=${item.kpi_accepted ? "да" : "нет"}${item.collector_error ? ` · ${ui(item.collector_error)}` : ""}</span>
<span class="badge ${statusClass(item.status)}">${ui(item.status || "UNKNOWN")}</span>
</div>
`).join("") : `<div class="row compact-row"><strong>История</strong><span class="muted">История качества агента за период отсутствует.</span><span class="badge status-unknown">UNKNOWN</span></div>`}</div>
`).join("") : `<div class="row compact-row"><strong>История</strong><span class="muted">История качества агента за период отсутствует.</span><span class="badge status-unknown">нет данных</span></div>`}</div>
</details>
</section>
`;
@@ -2076,7 +2091,7 @@ function renderAgentQualityNodes(nodes, summary) {
</div>
<div class="quality-decision">
<div><span class="muted">Всего узлов</span><strong>${escapeHtml(s.total_nodes ?? 0)}</strong></div>
<div><span class="muted">OK</span><strong>${escapeHtml(s.ok_nodes ?? 0)}</strong></div>
<div><span class="muted">В норме</span><strong>${escapeHtml(s.ok_nodes ?? 0)}</strong></div>
<div><span class="muted">Проблемных</span><strong>${escapeHtml(Number(s.degraded_nodes || 0) + Number(s.unknown_nodes || 0))}</strong></div>
<div><span class="muted">Показатели подтверждены</span><strong>${escapeHtml(s.accepted_kpi_nodes_pct ?? 0)}%</strong></div>
</div>
@@ -2106,7 +2121,7 @@ function renderAgentQualityNodes(nodes, summary) {
`).join("") : `
<tr>
<td>Нет данных</td>
<td><span class="badge status-unknown">UNKNOWN</span></td>
<td><span class="badge status-unknown">нет данных</span></td>
<td>unknown</td>
<td>-</td>
<td>нет</td>
@@ -2207,7 +2222,7 @@ function renderSecurityEventsSummary(summary, options = {}) {
: "Агрегированная сводка без сырых журналов и без автоматического создания инцидентов.";
const top = Array.isArray(s.top_departments) ? s.top_departments.slice(0, 5) : [];
const warning = fallback
? `<div class="quality-warning">События безопасности временно недоступны. Проверьте ClickHouse и переменные SECURITY_EVENTS_BACKEND/CLICKHOUSE_*.</div>`
? `<div class="quality-warning">События безопасности временно недоступны. Проверьте подключение источника событий.</div>`
: "";
if (options.compact) {
return `
@@ -2247,7 +2262,7 @@ function renderSecurityEventsSummary(summary, options = {}) {
</div>
${warning}
${s.error ? `<p class="muted small">Причина: ${ui(s.error)}</p>` : ""}
${disabled ? `<p class="muted small">Для включения задайте SECURITY_EVENTS_BACKEND=clickhouse и параметры CLICKHOUSE_*.</p>` : ""}
${disabled ? `<p class="muted small">Источник событий безопасности не включен в текущем режиме.</p>` : ""}
${options.compact ? "" : `
<div class="table-scroll">
<table class="data-table">
@@ -2313,7 +2328,7 @@ function renderBusinessRisk(items) {
`).join("") : `
<tr>
<td>Нет данных</td>
<td><span class="badge status-unknown">UNKNOWN</span></td>
<td><span class="badge status-unknown">нет данных</span></td>
<td>нет данных</td>
<td>0</td>
<td>Дождаться расчета подразделений.</td>
@@ -2370,10 +2385,10 @@ function renderRiskHeatmap(items) {
`).join("") : `
<tr>
<td>Нет данных</td>
<td>UNKNOWN</td>
<td>UNKNOWN</td>
<td>UNKNOWN</td>
<td><span class="badge status-unknown">UNKNOWN</span></td>
<td>нет данных</td>
<td>нет данных</td>
<td>нет данных</td>
<td><span class="badge status-unknown">нет данных</span></td>
<td>0</td>
<td>0</td>
<td>-</td>
@@ -2454,9 +2469,9 @@ function renderSecurityCorrelation(items) {
`).join("") : `
<tr>
<td>Нет данных</td>
<td>UNKNOWN</td>
<td>UNKNOWN</td>
<td><span class="badge status-unknown">UNKNOWN</span></td>
<td>нет данных</td>
<td>нет данных</td>
<td><span class="badge status-unknown">нет данных</span></td>
<td>проверить 0 · расследования 0</td>
<td>0</td>
<td>0/100</td>
@@ -2529,7 +2544,7 @@ function renderBusinessRiskTimeline(history, summary) {
<tr>
<td>-</td>
<td>История не накоплена</td>
<td><span class="badge status-unknown">UNKNOWN</span></td>
<td><span class="badge status-unknown">нет данных</span></td>
<td>Нужно дождаться daily history.</td>
</tr>
`}
@@ -2583,7 +2598,7 @@ function renderRiskIncidentCandidates(items) {
<td>-</td>
<td>Нет кандидатов</td>
<td>-</td>
<td><span class="badge status-ok">OK</span></td>
<td><span class="badge status-ok">в норме</span></td>
<td><span class="badge status-unknown">NEW</span></td>
<td>очередь проверки пуста</td>
<td>Действий не требуется.</td>
@@ -2645,6 +2660,7 @@ function renderCandidateReviewActions(item) {
<div class="button-row compact-actions">${actions.map(([status, label]) => `
<button class="small-button" data-review-status="${escapeHtml(status)}" data-candidate-id="${escapeHtml(id)}">${ui(label)}</button>
`).join("")}</div>
<button class="small-button" data-open-investigation="true">Открыть расследование</button>
<a class="small-button investigation-pack-button" href="${escapeHtml(packUrl)}" download>Скачать пакет расследования</a>
${createCase}
`;
@@ -2753,8 +2769,8 @@ function settingRows(report) {
return [
["Период расчета", period.label, period.key === "today" ? "оперативный дневной срез" : `${period.days} календарных дней`],
["Рабочий день", `${workdayHours} ч`, `роль: ${policy.role_label || activeRole.label || policy.default_role || "default"}`],
["Порог WARN", ">= 15 баллов", "любой ненормальный риск попадает в очередь проверки"],
["Порог FAIL", ">= 70 баллов", "высокий риск требует приоритетного разбора"],
["Порог внимания", ">= 15 баллов", "любой ненормальный риск попадает в очередь проверки"],
["Порог критического риска", ">= 70 баллов", "высокий риск требует приоритетного разбора"],
["Источник правил", policy.configured ? basename(policy.path) : "встроенные правила", risk.policy_configured ? `правила оценки риска: ${basename(risk.policy_path)}` : "правила оценки риска: встроенная модель"],
["Дата последнего пересчета", report?.generated_at_utc || "-", `версия политики: ${risk.policy_version || "ueba-rule-v1"}`],
];
@@ -2786,24 +2802,27 @@ async function refresh(options = {}) {
const content = document.getElementById("content");
const background = Boolean(options.background);
const stage = options.stage || "Получение данных";
const progress = (status, label, value) => {
if (!background) setLoadStatus(status, label, value);
};
try {
setLoadStatus("LOADING", stage, background ? 35 : 8);
progress("LOADING", stage, 8);
if (!background && content) content.innerHTML = renderLoadingContent(stage);
if (!state.links) {
setLoadStatus("LOADING", "Получение данных", 18);
progress("LOADING", "Получение данных", 18);
state.links = await loadJson("/links");
}
setLoadStatus("LOADING", "Расчёт показателей", 34);
progress("LOADING", "Расчёт показателей", 34);
const summary = await loadJson("/summary");
setLoadStatus("LOADING", "Расчёт показателей", 46);
progress("LOADING", "Расчёт показателей", 46);
state.readiness = {
bundle: await loadJson("/readiness/bundle").catch(error => ({ ok: false, error: error.message })),
verify: state.readiness?.verify || null
};
renderSummary(summary, state.readiness);
setLoadStatus("LOADING", "Формирование главного вывода", 68);
progress("LOADING", "Формирование главного вывода", 68);
const tabResult = await loadCurrentTab();
setLoadStatus("LOADING", "Подготовка разделов", 88);
progress("LOADING", "Подготовка разделов", 88);
if (!hasTabData(state.tab, tabResult)) {
setLoadStatus("EMPTY", "Данные отсутствуют", 100);
if (content) {
@@ -3031,7 +3050,7 @@ async function verifyReadinessBundle(button) {
};
const status = document.getElementById("readinessVerifyStatus");
if (status) {
status.textContent = `Проверено: контрольная сумма ${verify.checksum_verified ? "OK" : "FAIL"} · подпись ${verify.signature_verified ? "OK" : "FAIL"}`;
status.textContent = `Проверено: контрольная сумма ${verify.checksum_verified ? "подтверждена" : "не подтверждена"} · подпись ${verify.signature_verified ? "подтверждена" : "не подтверждена"}`;
}
button.disabled = false;
button.textContent = "Проверить пакет";