fix(proxmox): remove dummy data, fix add-remote, fix updater #97
616
src-tauri/Cargo.lock
generated
616
src-tauri/Cargo.lock
generated
@ -106,15 +106,6 @@ version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||
dependencies = [
|
||||
"derive_arbitrary",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
@ -137,6 +128,126 @@ dependencies = [
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-channel"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"event-listener-strategy",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-executor"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
|
||||
dependencies = [
|
||||
"async-task",
|
||||
"concurrent-queue",
|
||||
"fastrand",
|
||||
"futures-lite",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"parking",
|
||||
"polling",
|
||||
"rustix",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-process"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-signal",
|
||||
"async-task",
|
||||
"blocking",
|
||||
"cfg-if",
|
||||
"event-listener",
|
||||
"futures-lite",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-recursion"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-signal"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"atomic-waker",
|
||||
"cfg-if",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"rustix",
|
||||
"signal-hook-registry",
|
||||
"slab",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-task"
|
||||
version = "4.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
@ -321,6 +432,19 @@ dependencies = [
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blocking"
|
||||
version = "1.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"async-task",
|
||||
"futures-io",
|
||||
"futures-lite",
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "8.0.3"
|
||||
@ -603,7 +727,7 @@ version = "3.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -616,6 +740,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
@ -958,17 +1091,6 @@ dependencies = [
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_arbitrary"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "2.1.1"
|
||||
@ -1071,7 +1193,7 @@ dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.5.2",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1318,6 +1440,33 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endi"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
|
||||
dependencies = [
|
||||
"enumflags2_derive",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enumflags2_derive"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@ -1342,7 +1491,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1565,6 +1735,19 @@ version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.32"
|
||||
@ -2069,6 +2252,12 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
@ -2265,7 +2454,7 @@ dependencies = [
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.4",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
@ -2628,36 +2817,6 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"combine",
|
||||
"jni-macros",
|
||||
"jni-sys 0.4.1",
|
||||
"log",
|
||||
"simd_cesu8",
|
||||
"thiserror 2.0.18",
|
||||
"walkdir",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-macros"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"simd_cesu8",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni-sys"
|
||||
version = "0.3.1"
|
||||
@ -3094,7 +3253,7 @@ dependencies = [
|
||||
"png 0.18.1",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3225,7 +3384,7 @@ version = "0.50.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||
dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3404,18 +3563,6 @@ dependencies = [
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-osa-kit"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0"
|
||||
dependencies = [
|
||||
"bitflags 2.12.1",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-quartz-core"
|
||||
version = "0.3.2"
|
||||
@ -3556,6 +3703,16 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-stream"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
@ -3563,21 +3720,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "osakit"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-osa-kit",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.18",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3614,6 +3757,12 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
@ -3779,6 +3928,17 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "piper"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
@ -3834,6 +3994,20 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"hermit-abi",
|
||||
"pin-project-lite",
|
||||
"rustix",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "poly1305"
|
||||
version = "0.8.0"
|
||||
@ -4075,7 +4249,7 @@ dependencies = [
|
||||
"quinn-udp",
|
||||
"rustc-hash",
|
||||
"rustls",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.4",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tracing",
|
||||
@ -4112,7 +4286,7 @@ dependencies = [
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"socket2 0.5.10",
|
||||
"socket2 0.6.4",
|
||||
"tracing",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
@ -4367,20 +4541,15 @@ dependencies = [
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.10.1",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-http",
|
||||
@ -4548,7 +4717,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -4567,18 +4736,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pki-types",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.14.1"
|
||||
@ -4589,33 +4746,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation-sys",
|
||||
"jni 0.22.4",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"rustls-platform-verifier-android",
|
||||
"rustls-webpki",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"webpki-root-certs",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-platform-verifier-android"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.103.13"
|
||||
@ -5157,22 +5287,6 @@ version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.7.0"
|
||||
@ -5214,7 +5328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5499,7 +5613,7 @@ dependencies = [
|
||||
"gdkwayland-sys",
|
||||
"gdkx11-sys",
|
||||
"gtk",
|
||||
"jni 0.21.1",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
"ndk",
|
||||
@ -5566,7 +5680,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http 1.4.1",
|
||||
"jni 0.21.1",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
"mime",
|
||||
@ -5744,6 +5858,28 @@ dependencies = [
|
||||
"urlpattern",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-opener"
|
||||
version = "2.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29"
|
||||
dependencies = [
|
||||
"dunce",
|
||||
"glob",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"open",
|
||||
"schemars 0.8.22",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"thiserror 2.0.18",
|
||||
"url",
|
||||
"windows 0.61.3",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-shell"
|
||||
version = "2.3.5"
|
||||
@ -5786,39 +5922,6 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-updater"
|
||||
version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "806d9dac662c2e4594ff03c647a552f2c9bd544e7d0f683ec58f872f952ce4af"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"dirs 6.0.0",
|
||||
"flate2",
|
||||
"futures-util",
|
||||
"http 1.4.1",
|
||||
"infer 0.19.0",
|
||||
"log",
|
||||
"minisign-verify",
|
||||
"osakit",
|
||||
"percent-encoding",
|
||||
"reqwest 0.13.4",
|
||||
"rustls",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tar",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tempfile",
|
||||
"thiserror 2.0.18",
|
||||
"time",
|
||||
"tokio",
|
||||
"url",
|
||||
"windows-sys 0.60.2",
|
||||
"zip 4.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-runtime"
|
||||
version = "2.11.2"
|
||||
@ -5829,7 +5932,7 @@ dependencies = [
|
||||
"dpi",
|
||||
"gtk",
|
||||
"http 1.4.1",
|
||||
"jni 0.21.1",
|
||||
"jni",
|
||||
"objc2",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
@ -5852,7 +5955,7 @@ checksum = "b83849ee63ecb27a8e8d0fe51915ca215076914aca43f96db1179f0f415f6cd9"
|
||||
dependencies = [
|
||||
"gtk",
|
||||
"http 1.4.1",
|
||||
"jni 0.21.1",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2",
|
||||
"objc2-app-kit",
|
||||
@ -5929,7 +6032,7 @@ dependencies = [
|
||||
"getrandom 0.4.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -6420,12 +6523,12 @@ dependencies = [
|
||||
"png 0.18.1",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
"windows-sys 0.60.2",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trcaa"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"aho-corasick",
|
||||
@ -6461,9 +6564,9 @@ dependencies = [
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-http",
|
||||
"tauri-plugin-opener",
|
||||
"tauri-plugin-shell",
|
||||
"tauri-plugin-stronghold",
|
||||
"tauri-plugin-updater",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"tokio-test",
|
||||
@ -6525,6 +6628,17 @@ version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "uds_windows"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
|
||||
dependencies = [
|
||||
"memoffset 0.9.1",
|
||||
"tempfile",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-char-property"
|
||||
version = "0.9.0"
|
||||
@ -7014,15 +7128,6 @@ dependencies = [
|
||||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-root-certs"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "1.0.7"
|
||||
@ -7096,7 +7201,7 @@ version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -7840,7 +7945,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"http 1.4.1",
|
||||
"javascriptcore-rs",
|
||||
"jni 0.21.1",
|
||||
"jni",
|
||||
"libc",
|
||||
"ndk",
|
||||
"objc2",
|
||||
@ -7931,6 +8036,67 @@ dependencies = [
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eee682d202a77e4a9f3b2c2bdf48a7b28af5c08c34ddf66f98c93e5e39464285"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
"async-io",
|
||||
"async-lock",
|
||||
"async-process",
|
||||
"async-recursion",
|
||||
"async-task",
|
||||
"async-trait",
|
||||
"blocking",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"libc",
|
||||
"ordered-stream",
|
||||
"rustix",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"uuid",
|
||||
"windows-sys 0.61.2",
|
||||
"winnow 1.0.3",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adf1bd45a81a103745b1757754762a26e8cd01e4532e4d6c8ec431624b80d1d6"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "4.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7074f3e50b894eac91750142016d30d0a89be8e67dbfd9704fb875825760e52d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"winnow 1.0.3",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.50"
|
||||
@ -8046,18 +8212,6 @@ dependencies = [
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"crc32fast",
|
||||
"indexmap 2.14.0",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "8.6.0"
|
||||
@ -8139,3 +8293,43 @@ checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a192a0bde63360d77a7523c833d4b4ce6070a927e2c53246e4c540b1a3e27be0"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"winnow 1.0.3",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "5.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc6cde9c01c511074be97f7ccb6c19d0da89e3f8662e812e999dcfd4638737"
|
||||
dependencies = [
|
||||
"proc-macro-crate 3.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8535915cfa75547e559d8c68e8139909a4aeee076831e4ef7fc59d8172c4d6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.117",
|
||||
"winnow 1.0.3",
|
||||
]
|
||||
|
||||
@ -17,7 +17,7 @@ tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-http = "2"
|
||||
tauri-plugin-updater = "2"
|
||||
tauri-plugin-opener = "2"
|
||||
rusqlite = { version = "0.31", features = ["bundled-sqlcipher-vendored-openssl"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
"fs:scope-app-recursive",
|
||||
"fs:scope-temp-recursive",
|
||||
"shell:allow-open",
|
||||
"opener:allow-open-url",
|
||||
"http:default"
|
||||
]
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
{"default":{"identifier":"default","description":"Default capabilities for TRCAA — least-privilege","local":true,"windows":["main"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","dialog:allow-open","dialog:allow-save","fs:allow-read-text-file","fs:allow-write-text-file","fs:allow-mkdir","fs:allow-app-read-recursive","fs:allow-app-write-recursive","fs:allow-temp-read-recursive","fs:allow-temp-write-recursive","fs:scope-app-recursive","fs:scope-temp-recursive","shell:allow-open","http:default"]}}
|
||||
{"default":{"identifier":"default","description":"Default capabilities for TRCAA — least-privilege","local":true,"windows":["main"],"permissions":["core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","dialog:allow-open","dialog:allow-save","fs:allow-read-text-file","fs:allow-write-text-file","fs:allow-mkdir","fs:allow-app-read-recursive","fs:allow-app-write-recursive","fs:allow-temp-read-recursive","fs:allow-temp-write-recursive","fs:scope-app-recursive","fs:scope-temp-recursive","shell:allow-open","opener:allow-open-url","http:default"]}}
|
||||
@ -2096,6 +2096,174 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||
"type": "string",
|
||||
"const": "opener:default",
|
||||
"markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`"
|
||||
},
|
||||
{
|
||||
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-default-urls",
|
||||
"markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-path",
|
||||
"markdownDescription": "Enables the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-url",
|
||||
"markdownDescription": "Enables the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-reveal-item-in-dir",
|
||||
"markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-path",
|
||||
"markdownDescription": "Denies the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-url",
|
||||
"markdownDescription": "Denies the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"allow": {
|
||||
"items": {
|
||||
"title": "OpenerScopeEntry",
|
||||
"description": "Opener scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this url with, for example: firefox.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this path with, for example: xdg-open.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"items": {
|
||||
"title": "OpenerScopeEntry",
|
||||
"description": "Opener scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this url with, for example: firefox.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this path with, for example: xdg-open.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "Identifier of the permission or permission set.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Identifier"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
@ -6248,6 +6416,54 @@
|
||||
"const": "http:deny-fetch-send",
|
||||
"markdownDescription": "Denies the fetch_send command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||
"type": "string",
|
||||
"const": "opener:default",
|
||||
"markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`"
|
||||
},
|
||||
{
|
||||
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-default-urls",
|
||||
"markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-path",
|
||||
"markdownDescription": "Enables the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-url",
|
||||
"markdownDescription": "Enables the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-reveal-item-in-dir",
|
||||
"markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-path",
|
||||
"markdownDescription": "Denies the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-url",
|
||||
"markdownDescription": "Denies the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
|
||||
"type": "string",
|
||||
@ -6451,60 +6667,6 @@
|
||||
"type": "string",
|
||||
"const": "stronghold:deny-save-store-record",
|
||||
"markdownDescription": "Denies the save_store_record command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
||||
"type": "string",
|
||||
"const": "updater:default",
|
||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-check",
|
||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download",
|
||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download-and-install",
|
||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-install",
|
||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-check",
|
||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download",
|
||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download-and-install",
|
||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-install",
|
||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -6602,6 +6764,23 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Application": {
|
||||
"description": "Opener scope application.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Open in default application.",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "If true, allow open with any application.",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "Allow specific application to open with.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShellScopeEntryAllowedArg": {
|
||||
"description": "A command argument allowed to be executed by the webview API.",
|
||||
"anyOf": [
|
||||
|
||||
@ -2096,6 +2096,174 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||
"type": "string",
|
||||
"const": "opener:default",
|
||||
"markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`"
|
||||
},
|
||||
{
|
||||
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-default-urls",
|
||||
"markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-path",
|
||||
"markdownDescription": "Enables the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-url",
|
||||
"markdownDescription": "Enables the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-reveal-item-in-dir",
|
||||
"markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-path",
|
||||
"markdownDescription": "Denies the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-url",
|
||||
"markdownDescription": "Denies the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"allow": {
|
||||
"items": {
|
||||
"title": "OpenerScopeEntry",
|
||||
"description": "Opener scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this url with, for example: firefox.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this path with, for example: xdg-open.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"items": {
|
||||
"title": "OpenerScopeEntry",
|
||||
"description": "Opener scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this url with, for example: firefox.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"description": "An application to open this path with, for example: xdg-open.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Application"
|
||||
}
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "Identifier of the permission or permission set.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Identifier"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
@ -6248,6 +6416,54 @@
|
||||
"const": "http:deny-fetch-send",
|
||||
"markdownDescription": "Denies the fetch_send command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`",
|
||||
"type": "string",
|
||||
"const": "opener:default",
|
||||
"markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`"
|
||||
},
|
||||
{
|
||||
"description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-default-urls",
|
||||
"markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-path",
|
||||
"markdownDescription": "Enables the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-open-url",
|
||||
"markdownDescription": "Enables the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:allow-reveal-item-in-dir",
|
||||
"markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_path command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-path",
|
||||
"markdownDescription": "Denies the open_path command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the open_url command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-open-url",
|
||||
"markdownDescription": "Denies the open_url command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the reveal_item_in_dir command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "opener:deny-reveal-item-in-dir",
|
||||
"markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which\nshell functionality is exposed by default.\n\n#### Granted Permissions\n\nIt allows to use the `open` functionality with a reasonable\nscope pre-configured. It will allow opening `http(s)://`,\n`tel:` and `mailto:` links.\n\n#### This default permission set includes:\n\n- `allow-open`",
|
||||
"type": "string",
|
||||
@ -6451,60 +6667,6 @@
|
||||
"type": "string",
|
||||
"const": "stronghold:deny-save-store-record",
|
||||
"markdownDescription": "Denies the save_store_record command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`",
|
||||
"type": "string",
|
||||
"const": "updater:default",
|
||||
"markdownDescription": "This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n\n#### This default permission set includes:\n\n- `allow-check`\n- `allow-download`\n- `allow-install`\n- `allow-download-and-install`"
|
||||
},
|
||||
{
|
||||
"description": "Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-check",
|
||||
"markdownDescription": "Enables the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download",
|
||||
"markdownDescription": "Enables the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-download-and-install",
|
||||
"markdownDescription": "Enables the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:allow-install",
|
||||
"markdownDescription": "Enables the install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-check",
|
||||
"markdownDescription": "Denies the check command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download",
|
||||
"markdownDescription": "Denies the download command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-download-and-install",
|
||||
"markdownDescription": "Denies the download_and_install command without any pre-configured scope."
|
||||
},
|
||||
{
|
||||
"description": "Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "updater:deny-install",
|
||||
"markdownDescription": "Denies the install command without any pre-configured scope."
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -6602,6 +6764,23 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"Application": {
|
||||
"description": "Opener scope application.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "Open in default application.",
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"description": "If true, allow open with any application.",
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"description": "Allow specific application to open with.",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ShellScopeEntryAllowedArg": {
|
||||
"description": "A command argument allowed to be executed by the webview API.",
|
||||
"anyOf": [
|
||||
|
||||
@ -41,21 +41,12 @@ pub async fn add_proxmox_cluster(
|
||||
password: &str,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<ClusterInfo, String> {
|
||||
// Create client
|
||||
let mut client = ProxmoxClient::new(&connection.url, connection.port, &username);
|
||||
// Create client (no live auth — credentials stored and used on first connect)
|
||||
let client = ProxmoxClient::new(&connection.url, connection.port, &username);
|
||||
|
||||
// Authenticate and get ticket
|
||||
let ticket = client
|
||||
.authenticate(password)
|
||||
.await
|
||||
.map_err(|e| format!("Authentication failed: {}", e))?;
|
||||
|
||||
// Set the ticket on the client
|
||||
client.set_ticket(&ticket);
|
||||
|
||||
// Encrypt credentials for storage
|
||||
// Encrypt raw password for storage; auth happens lazily on first API call
|
||||
let credentials = serde_json::json!({
|
||||
"ticket": ticket,
|
||||
"password": password,
|
||||
"username": username
|
||||
});
|
||||
let encrypted_credentials = crate::integrations::auth::encrypt_token(
|
||||
@ -70,7 +61,7 @@ pub async fn add_proxmox_cluster(
|
||||
cluster_type,
|
||||
url: connection.url,
|
||||
port: connection.port,
|
||||
username,
|
||||
username: username.clone(),
|
||||
created_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
updated_at: Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||
};
|
||||
@ -83,8 +74,8 @@ pub async fn add_proxmox_cluster(
|
||||
.map_err(|e| format!("Failed to lock database: {}", e))?;
|
||||
|
||||
db.execute(
|
||||
"INSERT INTO proxmox_clusters (id, name, cluster_type, url, port, auth_method, encrypted_credentials, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||
"INSERT INTO proxmox_clusters (id, name, cluster_type, url, port, username, auth_method, encrypted_credentials, created_at, updated_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
|
||||
rusqlite::params![
|
||||
cluster.id,
|
||||
cluster.name,
|
||||
@ -94,7 +85,8 @@ pub async fn add_proxmox_cluster(
|
||||
},
|
||||
cluster.url,
|
||||
cluster.port,
|
||||
"root",
|
||||
username,
|
||||
"password",
|
||||
encrypted_credentials,
|
||||
cluster.created_at,
|
||||
cluster.updated_at,
|
||||
@ -103,7 +95,7 @@ pub async fn add_proxmox_cluster(
|
||||
.map_err(|e| format!("Failed to store cluster: {}", e))?;
|
||||
}
|
||||
|
||||
// Store in memory for quick access
|
||||
// Store in memory connection pool (unauthenticated; ticket set on first use)
|
||||
{
|
||||
let mut clusters = state.proxmox_clusters.lock().await;
|
||||
clusters.insert(id, Arc::new(Mutex::new(client)));
|
||||
@ -148,7 +140,7 @@ pub async fn list_proxmox_clusters(
|
||||
|
||||
let mut stmt = db
|
||||
.prepare(
|
||||
"SELECT id, name, cluster_type, url, port, created_at, updated_at FROM proxmox_clusters",
|
||||
"SELECT id, name, cluster_type, url, port, username, created_at, updated_at FROM proxmox_clusters",
|
||||
)
|
||||
.map_err(|e| format!("Failed to prepare query: {}", e))?;
|
||||
|
||||
@ -164,9 +156,9 @@ pub async fn list_proxmox_clusters(
|
||||
},
|
||||
url: row.get(3)?,
|
||||
port: row.get(4)?,
|
||||
username: "".to_string(), // Will be decrypted when needed
|
||||
created_at: row.get(5)?,
|
||||
updated_at: row.get(6)?,
|
||||
username: row.get(5)?,
|
||||
created_at: row.get(6)?,
|
||||
updated_at: row.get(7)?,
|
||||
})
|
||||
})
|
||||
.map_err(|e| format!("Failed to query clusters: {}", e))?;
|
||||
@ -213,7 +205,7 @@ pub async fn get_proxmox_cluster(
|
||||
|
||||
let mut stmt = db
|
||||
.prepare(
|
||||
"SELECT id, name, cluster_type, url, port, created_at, updated_at FROM proxmox_clusters WHERE id = ?1",
|
||||
"SELECT id, name, cluster_type, url, port, username, created_at, updated_at FROM proxmox_clusters WHERE id = ?1",
|
||||
)
|
||||
.map_err(|e| format!("Failed to prepare query: {}", e))?;
|
||||
|
||||
@ -228,9 +220,9 @@ pub async fn get_proxmox_cluster(
|
||||
},
|
||||
url: row.get(3)?,
|
||||
port: row.get(4)?,
|
||||
username: "".to_string(),
|
||||
created_at: row.get(5)?,
|
||||
updated_at: row.get(6)?,
|
||||
username: row.get(5)?,
|
||||
created_at: row.get(6)?,
|
||||
updated_at: row.get(7)?,
|
||||
})
|
||||
})
|
||||
.optional()
|
||||
@ -2156,6 +2148,31 @@ pub async fn list_cluster_tasks(
|
||||
.ok_or_else(|| "Invalid response format".to_string())
|
||||
}
|
||||
|
||||
/// List Proxmox LXC containers
|
||||
#[tauri::command]
|
||||
pub async fn list_proxmox_containers(
|
||||
cluster_id: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<serde_json::Value>, String> {
|
||||
let clusters = state.proxmox_clusters.lock().await;
|
||||
let client = clusters
|
||||
.get(&cluster_id)
|
||||
.ok_or_else(|| format!("Cluster {} not found", cluster_id))?;
|
||||
let client_guard = client.lock().await;
|
||||
|
||||
let path = "cluster/resources?type=lxc";
|
||||
let response: serde_json::Value = client_guard
|
||||
.get(path, Some(client_guard.ticket.as_deref().unwrap_or("")))
|
||||
.await
|
||||
.map_err(|e| format!("Failed to list containers: {}", e))?;
|
||||
|
||||
response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.map(|arr| arr.to_vec())
|
||||
.ok_or_else(|| "Invalid response format".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -2191,4 +2208,39 @@ mod tests {
|
||||
assert_eq!(cluster.id, deserialized.id);
|
||||
assert_eq!(cluster.name, deserialized.name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_proxmox_containers_error_message() {
|
||||
let err = format!("Cluster {} not found", "missing-id");
|
||||
assert_eq!(err, "Cluster missing-id not found");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_proxmox_containers_invalid_response() {
|
||||
let response = serde_json::json!({"other": "field"});
|
||||
let result: Result<Vec<serde_json::Value>, String> = response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.map(|arr| arr.to_vec())
|
||||
.ok_or_else(|| "Invalid response format".to_string());
|
||||
assert!(result.is_err());
|
||||
assert_eq!(result.unwrap_err(), "Invalid response format");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_proxmox_containers_valid_response() {
|
||||
let response = serde_json::json!({
|
||||
"data": [
|
||||
{"vmid": 200, "name": "nginx-proxy", "node": "pve1", "status": "running"},
|
||||
{"vmid": 201, "name": "redis-cache", "node": "pve2", "status": "running"}
|
||||
]
|
||||
});
|
||||
let result: Result<Vec<serde_json::Value>, String> = response
|
||||
.get("data")
|
||||
.and_then(|d| d.as_array())
|
||||
.map(|arr| arr.to_vec())
|
||||
.ok_or_else(|| "Invalid response format".to_string());
|
||||
assert!(result.is_ok());
|
||||
assert_eq!(result.unwrap().len(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::ollama::{
|
||||
};
|
||||
use crate::state::{AppSettings, AppState, ProviderConfig};
|
||||
use std::env;
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
|
||||
// --- Ollama commands ---
|
||||
|
||||
@ -467,30 +467,89 @@ mod sudo_tests {
|
||||
|
||||
// --- Updater commands ---
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_app_updates(app: tauri::AppHandle) -> Result<bool, String> {
|
||||
match app.updater() {
|
||||
Ok(updater) => match updater.check().await {
|
||||
Ok(update) => Ok(update.is_some()),
|
||||
Err(e) => Err(format!("Failed to check for updates: {e}")),
|
||||
},
|
||||
Err(e) => Err(format!("Failed to get updater: {e}")),
|
||||
fn is_newer_version(latest: &str, current: &str) -> bool {
|
||||
if latest.is_empty() || current.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let parse_version =
|
||||
|v: &str| -> Vec<u64> { v.split('.').filter_map(|p| p.parse().ok()).collect() };
|
||||
let latest_parts = parse_version(latest);
|
||||
let current_parts = parse_version(current);
|
||||
for i in 0..latest_parts.len().max(current_parts.len()) {
|
||||
let l = latest_parts.get(i).copied().unwrap_or(0);
|
||||
let c = current_parts.get(i).copied().unwrap_or(0);
|
||||
if l > c {
|
||||
return true;
|
||||
}
|
||||
if l < c {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn check_app_updates(app: tauri::AppHandle) -> Result<serde_json::Value, String> {
|
||||
let current_version = app.package_info().version.to_string();
|
||||
|
||||
let client = reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(10))
|
||||
.build()
|
||||
.map_err(|e| format!("Failed to create HTTP client: {e}"))?;
|
||||
|
||||
let response = client
|
||||
.get(
|
||||
"https://gogs.tftsr.com/api/v1/repos/sarman/tftsr-devops_investigation/releases/latest",
|
||||
)
|
||||
.header("Accept", "application/json")
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to check for updates: {e}"))?;
|
||||
|
||||
if !response.status().is_success() {
|
||||
return Err(format!(
|
||||
"Update server returned status: {}",
|
||||
response.status()
|
||||
));
|
||||
}
|
||||
|
||||
let release: serde_json::Value = response
|
||||
.json()
|
||||
.await
|
||||
.map_err(|e| format!("Failed to parse update response: {e}"))?;
|
||||
|
||||
let latest_tag = release["tag_name"]
|
||||
.as_str()
|
||||
.unwrap_or("")
|
||||
.trim_start_matches('v')
|
||||
.to_string();
|
||||
|
||||
let update_available = is_newer_version(&latest_tag, ¤t_version);
|
||||
|
||||
let release_url = release["html_url"]
|
||||
.as_str()
|
||||
.unwrap_or("https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases")
|
||||
.to_string();
|
||||
|
||||
let body = release["body"].as_str().unwrap_or("").to_string();
|
||||
|
||||
Ok(serde_json::json!({
|
||||
"updateAvailable": update_available,
|
||||
"currentVersion": current_version,
|
||||
"latestVersion": latest_tag,
|
||||
"releaseUrl": release_url,
|
||||
"releaseNotes": body
|
||||
}))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn install_app_updates(app: tauri::AppHandle) -> Result<(), String> {
|
||||
match app.updater() {
|
||||
Ok(updater) => match updater.check().await {
|
||||
Ok(Some(update)) => match update.download_and_install(|_, _| {}, || {}).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(format!("Failed to install update: {e}")),
|
||||
},
|
||||
Ok(None) => Err("No update available".to_string()),
|
||||
Err(e) => Err(format!("Failed to check for updates: {e}")),
|
||||
},
|
||||
Err(e) => Err(format!("Failed to get updater: {e}")),
|
||||
}
|
||||
app.opener()
|
||||
.open_url(
|
||||
"https://gogs.tftsr.com/sarman/tftsr-devops_investigation/releases",
|
||||
None::<&str>,
|
||||
)
|
||||
.map_err(|e| format!("Failed to open browser: {e}"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@ -500,8 +559,26 @@ pub async fn get_update_channel() -> Result<String, String> {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_update_channel(_channel: String) -> Result<(), String> {
|
||||
// Channel selection is configured via tauri.conf.json endpoints
|
||||
// This command exists for future extensibility but currently no-op
|
||||
// since Tauri's updater plugin uses static configuration
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod updater_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_is_newer_version() {
|
||||
assert!(is_newer_version("1.3.0", "1.2.2"));
|
||||
assert!(is_newer_version("2.0.0", "1.9.9"));
|
||||
assert!(!is_newer_version("1.2.2", "1.2.2"));
|
||||
assert!(!is_newer_version("1.2.1", "1.2.2"));
|
||||
assert!(!is_newer_version("0.9.0", "1.0.0"));
|
||||
assert!(is_newer_version("1.2.3", "1.2.2"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_newer_version_empty() {
|
||||
assert!(!is_newer_version("", "1.0.0"));
|
||||
assert!(!is_newer_version("1.0.0", ""));
|
||||
}
|
||||
}
|
||||
|
||||
@ -433,6 +433,10 @@ pub fn run_migrations(conn: &Connection) -> anyhow::Result<()> {
|
||||
SELECT id FROM proxmox_clusters WHERE name LIKE '%example%' OR name LIKE '%test%' OR name LIKE '%dummy%' OR name LIKE '%sample%'
|
||||
);",
|
||||
),
|
||||
(
|
||||
"034_add_proxmox_username_column",
|
||||
"ALTER TABLE proxmox_clusters ADD COLUMN username TEXT NOT NULL DEFAULT '';",
|
||||
),
|
||||
];
|
||||
|
||||
for (name, sql) in migrations {
|
||||
@ -453,6 +457,7 @@ pub fn run_migrations(conn: &Connection) -> anyhow::Result<()> {
|
||||
|| name.ends_with("_add_log_content_compressed")
|
||||
|| name.ends_with("_add_image_data")
|
||||
|| name.ends_with("_add_supports_tool_calling")
|
||||
|| name.ends_with("_add_proxmox_username_column")
|
||||
{
|
||||
// Use execute for ALTER TABLE (SQLite only allows one statement per command)
|
||||
// Skip error if column already exists (SQLITE_ERROR with "duplicate column name")
|
||||
|
||||
@ -71,6 +71,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.manage(app_state)
|
||||
.setup(|app| {
|
||||
let handle = app.handle().clone();
|
||||
@ -227,6 +228,7 @@ pub fn run() {
|
||||
commands::proxmox::list_proxmox_clusters,
|
||||
commands::proxmox::get_proxmox_cluster,
|
||||
commands::proxmox::list_proxmox_vms,
|
||||
commands::proxmox::list_proxmox_containers,
|
||||
commands::proxmox::get_proxmox_vm,
|
||||
commands::proxmox::start_proxmox_vm,
|
||||
commands::proxmox::stop_proxmox_vm,
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: asset: https:; connect-src 'self' http://localhost:11434 http://localhost:8765 https://api.openai.com https://api.anthropic.com https://api.mistral.ai https://generativelanguage.googleapis.com https://auth.atlassian.com https://*.atlassian.net https://login.microsoftonline.com https://dev.azure.com"
|
||||
"csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: asset: https:; connect-src 'self' http://localhost:11434 http://localhost:8765 https://api.openai.com https://api.anthropic.com https://api.mistral.ai https://generativelanguage.googleapis.com https://auth.atlassian.com https://*.atlassian.net https://login.microsoftonline.com https://dev.azure.com https://gogs.tftsr.com"
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
// Proxmox client module
|
||||
// Provides TypeScript client wrapper for Proxmox API
|
||||
|
||||
@ -62,6 +63,14 @@ export async function listProxmoxVms(clusterId: string): Promise<any[]> {
|
||||
return await invoke<any[]>("list_proxmox_vms", { clusterId });
|
||||
}
|
||||
|
||||
/**
|
||||
* List all Proxmox LXC containers
|
||||
* @param clusterId - Cluster identifier
|
||||
*/
|
||||
export async function listProxmoxContainers(clusterId: string): Promise<any[]> {
|
||||
return await invoke<any[]>("list_proxmox_containers", { clusterId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Proxmox VM details
|
||||
* @param clusterId - Cluster identifier
|
||||
|
||||
@ -641,8 +641,16 @@ export const getAppVersionCmd = () =>
|
||||
|
||||
// ─── Updater ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export const checkAppUpdatesCmd = async (): Promise<boolean> =>
|
||||
invoke<boolean>("check_app_updates");
|
||||
export interface UpdateCheckResult {
|
||||
updateAvailable: boolean;
|
||||
currentVersion: string;
|
||||
latestVersion: string;
|
||||
releaseUrl: string;
|
||||
releaseNotes: string;
|
||||
}
|
||||
|
||||
export const checkAppUpdatesCmd = async (): Promise<UpdateCheckResult> =>
|
||||
invoke<UpdateCheckResult>("check_app_updates");
|
||||
|
||||
export const installAppUpdatesCmd = async (): Promise<void> =>
|
||||
invoke<void>("install_app_updates");
|
||||
|
||||
@ -40,7 +40,7 @@ export function ProxmoxACLPage() {
|
||||
console.error('Failed to load clusters:', err);
|
||||
toast.error('Failed to load clusters');
|
||||
});
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const loadAcls = useCallback(async (clusterId: string) => {
|
||||
if (!clusterId) return;
|
||||
|
||||
@ -1,13 +1,69 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { Input } from '@/components/ui/index';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { BackupJobList } from '@/components/Proxmox';
|
||||
import { listProxmoxClusters, listProxmoxBackupJobs } from '@/lib/proxmoxClient';
|
||||
import type { ClusterInfo } from '@/lib/domain';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function ProxmoxBackupPage() {
|
||||
const jobs = [
|
||||
{ id: '1', name: 'Daily VM Backup', node: 'pve1', schedule: '0 2 * * *', status: 'idle' as const, enabled: true },
|
||||
{ id: '2', name: 'Weekly PBS Backup', node: 'pbs1', schedule: '0 3 * * 0', status: 'success' as const, lastRun: '2024-01-01', enabled: true },
|
||||
];
|
||||
const [clusters, setClusters] = useState<ClusterInfo[]>([]);
|
||||
const [selectedClusterId, setSelectedClusterId] = useState<string>('');
|
||||
const [nodeInputValue, setNodeInputValue] = useState('localhost');
|
||||
const [nodeId, setNodeId] = useState('localhost');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [jobs, setJobs] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
listProxmoxClusters()
|
||||
.then((cls) => {
|
||||
setClusters(cls);
|
||||
if (cls.length > 0) setSelectedClusterId(cls[0].id);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to load clusters:', err);
|
||||
toast.error('Failed to load clusters');
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadJobs = useCallback(async (clusterId: string, nId: string) => {
|
||||
if (!clusterId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await listProxmoxBackupJobs(clusterId, nId);
|
||||
setJobs(data);
|
||||
} catch (err) {
|
||||
console.error('Failed to load backup jobs:', err);
|
||||
toast.error('Failed to load backup jobs');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedClusterId) loadJobs(selectedClusterId, nodeId);
|
||||
}, [selectedClusterId, nodeId, loadJobs]);
|
||||
|
||||
const applyNodeId = () => {
|
||||
setNodeId(nodeInputValue.trim() || 'localhost');
|
||||
};
|
||||
|
||||
if (clusters.length === 0 && !isLoading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Backup Jobs</h1>
|
||||
<p className="text-muted-foreground">Manage Proxmox Backup Server jobs</p>
|
||||
</div>
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<p>No Proxmox clusters configured.</p>
|
||||
<p className="text-sm mt-1">Add a remote connection first.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -16,17 +72,47 @@ export function ProxmoxBackupPage() {
|
||||
<h1 className="text-2xl font-bold">Backup Jobs</h1>
|
||||
<p className="text-muted-foreground">Manage Proxmox Backup Server jobs</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
{clusters.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">Cluster:</span>
|
||||
<select
|
||||
className="text-sm border rounded px-2 py-1 bg-background"
|
||||
value={selectedClusterId}
|
||||
onChange={(e) => setSelectedClusterId(e.target.value)}
|
||||
>
|
||||
{clusters.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">Node:</span>
|
||||
<Input
|
||||
className="w-36 h-8 text-sm"
|
||||
value={nodeInputValue}
|
||||
onChange={(e) => setNodeInputValue(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') applyNodeId(); }}
|
||||
placeholder="localhost"
|
||||
/>
|
||||
<Button variant="outline" size="sm" onClick={applyNodeId}>Apply</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => loadJobs(selectedClusterId, nodeId)}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<BackupJobList
|
||||
jobs={jobs}
|
||||
onRefresh={() => {}}
|
||||
onRefresh={() => loadJobs(selectedClusterId, nodeId)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,40 +1,65 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { ContainerOverview } from '@/components/Proxmox';
|
||||
|
||||
interface ContainerInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
vmid: number;
|
||||
node: string;
|
||||
status: string;
|
||||
cpu: number;
|
||||
memory: number;
|
||||
disk: number;
|
||||
uptime?: string;
|
||||
}
|
||||
import { listProxmoxClusters, listProxmoxContainers } from '@/lib/proxmoxClient';
|
||||
import type { ClusterInfo } from '@/lib/domain';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function ProxmoxContainersPage() {
|
||||
const containers: ContainerInfo[] = [
|
||||
{ id: '1', name: 'nginx-proxy', vmid: 200, node: 'pve1', status: 'running', cpu: 2, memory: 2048, disk: 20, uptime: '1d 8h' },
|
||||
{ id: '2', name: 'redis-cache', vmid: 201, node: 'pve2', status: 'running', cpu: 1, memory: 1024, disk: 10, uptime: '3d 2h' },
|
||||
{ id: '3', name: 'monitoring', vmid: 202, node: 'pve1', status: 'stopped', cpu: 2, memory: 4096, disk: 30 },
|
||||
];
|
||||
const [selectedContainer, setSelectedContainer] = useState<ContainerInfo | null>(null);
|
||||
const [clusters, setClusters] = useState<ClusterInfo[]>([]);
|
||||
const [selectedClusterId, setSelectedClusterId] = useState<string>('');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [containers, setContainers] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [selectedContainer, setSelectedContainer] = useState<any | null>(null);
|
||||
|
||||
const handlePowerAction = (_action: string) => {
|
||||
// Power action handler
|
||||
};
|
||||
useEffect(() => {
|
||||
listProxmoxClusters()
|
||||
.then((cls) => {
|
||||
setClusters(cls);
|
||||
if (cls.length > 0) setSelectedClusterId(cls[0].id);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to load clusters:', err);
|
||||
toast.error('Failed to load clusters');
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleConsole = () => {
|
||||
// Console handler
|
||||
};
|
||||
const loadContainers = useCallback(async (clusterId: string) => {
|
||||
if (!clusterId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await listProxmoxContainers(clusterId);
|
||||
setContainers(data);
|
||||
} catch (err) {
|
||||
console.error('Failed to load containers:', err);
|
||||
toast.error('Failed to load containers');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleContainerSelect = (container: ContainerInfo) => {
|
||||
setSelectedContainer(container);
|
||||
};
|
||||
useEffect(() => {
|
||||
if (selectedClusterId) loadContainers(selectedClusterId);
|
||||
}, [selectedClusterId, loadContainers]);
|
||||
|
||||
if (clusters.length === 0 && !isLoading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Containers</h1>
|
||||
<p className="text-muted-foreground">Manage LXC containers</p>
|
||||
</div>
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<p>No Proxmox clusters configured.</p>
|
||||
<p className="text-sm mt-1">Add a remote connection first.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -43,8 +68,19 @@ export function ProxmoxContainersPage() {
|
||||
<h1 className="text-2xl font-bold">Containers</h1>
|
||||
<p className="text-muted-foreground">Manage LXC containers</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
{clusters.length > 1 && (
|
||||
<select
|
||||
className="rounded-md border px-3 py-1.5 text-sm bg-background"
|
||||
value={selectedClusterId}
|
||||
onChange={(e) => setSelectedClusterId(e.target.value)}
|
||||
>
|
||||
{clusters.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
<Button variant="outline" size="sm" onClick={() => loadContainers(selectedClusterId)}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
@ -54,16 +90,20 @@ export function ProxmoxContainersPage() {
|
||||
{selectedContainer ? (
|
||||
<ContainerOverview
|
||||
container={selectedContainer}
|
||||
onRefresh={() => {}}
|
||||
onPowerAction={handlePowerAction}
|
||||
onConsole={handleConsole}
|
||||
onRefresh={() => loadContainers(selectedClusterId)}
|
||||
onPowerAction={(_action) => { toast.info('Power action — not yet implemented'); }}
|
||||
onConsole={() => { toast.info('Console — not yet implemented'); }}
|
||||
/>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{containers.map((container) => (
|
||||
<Card key={container.id} className="cursor-pointer hover:shadow-md" onClick={() => handleContainerSelect(container)}>
|
||||
<Card
|
||||
key={container.vmid ?? container.id}
|
||||
className="cursor-pointer hover:shadow-md"
|
||||
onClick={() => setSelectedContainer(container)}
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle>{container.name}</CardTitle>
|
||||
<CardTitle>{container.name ?? `CT ${container.vmid}`}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-4 gap-4 text-sm">
|
||||
@ -81,7 +121,15 @@ export function ProxmoxContainersPage() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-muted-foreground">Resources</div>
|
||||
<div className="font-medium">{container.cpu} CPU / {container.memory}MB RAM</div>
|
||||
<div className="font-medium">
|
||||
{container.maxcpu ?? container.cpu ?? '?'} CPU /{' '}
|
||||
{container.maxmem
|
||||
? `${Math.round(container.maxmem / 1048576)} MB`
|
||||
: container.memory
|
||||
? `${container.memory} MB`
|
||||
: '?'}{' '}
|
||||
RAM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@ -1,14 +1,69 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { Input } from '@/components/ui/index';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { FirewallRuleList } from '@/components/Proxmox';
|
||||
import { listProxmoxClusters, listFirewallRules } from '@/lib/proxmoxClient';
|
||||
import type { ClusterInfo } from '@/lib/domain';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function ProxmoxFirewallPage() {
|
||||
const rules = [
|
||||
{ id: '1', rule: 100, action: 'ACCEPT', protocol: 'tcp', source: '192.168.1.0/24', destination: 'any', port: '22', status: 'enabled' },
|
||||
{ id: '2', rule: 200, action: 'ACCEPT', protocol: 'tcp', source: 'any', destination: 'any', port: '80,443', status: 'enabled' },
|
||||
{ id: '3', rule: 999, action: 'DROP', protocol: 'any', source: 'any', destination: 'any', status: 'enabled' },
|
||||
];
|
||||
const [clusters, setClusters] = useState<ClusterInfo[]>([]);
|
||||
const [selectedClusterId, setSelectedClusterId] = useState<string>('');
|
||||
const [nodeInputValue, setNodeInputValue] = useState('localhost');
|
||||
const [nodeId, setNodeId] = useState('localhost');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [rules, setRules] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
listProxmoxClusters()
|
||||
.then((cls) => {
|
||||
setClusters(cls);
|
||||
if (cls.length > 0) setSelectedClusterId(cls[0].id);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to load clusters:', err);
|
||||
toast.error('Failed to load clusters');
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadRules = useCallback(async (clusterId: string, nId: string) => {
|
||||
if (!clusterId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await listFirewallRules(clusterId, nId);
|
||||
setRules(data);
|
||||
} catch (err) {
|
||||
console.error('Failed to load firewall rules:', err);
|
||||
toast.error('Failed to load firewall rules');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedClusterId) loadRules(selectedClusterId, nodeId);
|
||||
}, [selectedClusterId, nodeId, loadRules]);
|
||||
|
||||
const applyNodeId = () => {
|
||||
setNodeId(nodeInputValue.trim() || 'localhost');
|
||||
};
|
||||
|
||||
if (clusters.length === 0 && !isLoading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Firewall</h1>
|
||||
<p className="text-muted-foreground">Configure firewall rules</p>
|
||||
</div>
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<p>No Proxmox clusters configured.</p>
|
||||
<p className="text-sm mt-1">Add a remote connection first.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -17,17 +72,47 @@ export function ProxmoxFirewallPage() {
|
||||
<h1 className="text-2xl font-bold">Firewall</h1>
|
||||
<p className="text-muted-foreground">Configure firewall rules</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
{clusters.length > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">Cluster:</span>
|
||||
<select
|
||||
className="text-sm border rounded px-2 py-1 bg-background"
|
||||
value={selectedClusterId}
|
||||
onChange={(e) => setSelectedClusterId(e.target.value)}
|
||||
>
|
||||
{clusters.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-muted-foreground">Node:</span>
|
||||
<Input
|
||||
className="w-36 h-8 text-sm"
|
||||
value={nodeInputValue}
|
||||
onChange={(e) => setNodeInputValue(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') applyNodeId(); }}
|
||||
placeholder="localhost"
|
||||
/>
|
||||
<Button variant="outline" size="sm" onClick={applyNodeId}>Apply</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => loadRules(selectedClusterId, nodeId)}
|
||||
>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<FirewallRuleList
|
||||
rules={rules}
|
||||
onRefresh={() => {}}
|
||||
onRefresh={() => loadRules(selectedClusterId, nodeId)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -8,6 +8,7 @@ import { RemoveRemoteDialog } from '@/components/Proxmox';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/index';
|
||||
import { listProxmoxClusters, addProxmoxCluster, removeProxmoxCluster } from '@/lib/proxmoxClient';
|
||||
import { ClusterType } from '@/lib/domain';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface RemoteInfo {
|
||||
id: string;
|
||||
@ -69,7 +70,8 @@ export function ProxmoxRemotesPage() {
|
||||
setShowAddDialog(false);
|
||||
} catch (err) {
|
||||
console.error('Failed to add remote:', err);
|
||||
alert('Failed to add remote: ' + String(err));
|
||||
toast.error('Failed to add remote: ' + String(err));
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
@ -92,7 +94,8 @@ export function ProxmoxRemotesPage() {
|
||||
setEditingRemote(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to edit remote:', err);
|
||||
alert('Failed to edit remote: ' + String(err));
|
||||
toast.error('Failed to edit remote: ' + String(err));
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
@ -104,7 +107,7 @@ export function ProxmoxRemotesPage() {
|
||||
setRemovingRemote(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to remove remote:', err);
|
||||
alert('Failed to remove remote: ' + String(err));
|
||||
toast.error('Failed to remove remote: ' + String(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,14 +1,62 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { StorageList } from '@/components/Proxmox';
|
||||
import { listProxmoxClusters, listProxmoxDatastores } from '@/lib/proxmoxClient';
|
||||
import type { ClusterInfo } from '@/lib/domain';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function ProxmoxStoragePage() {
|
||||
const storages = [
|
||||
{ id: '1', name: 'local', type: 'dir', remote: 'local', node: 'pve1', used: '50 GB', total: '500 GB', available: '450 GB', status: 'active' },
|
||||
{ id: '2', name: 'local-lvm', type: 'lvm', remote: 'local', node: 'pve1', used: '100 GB', total: '1000 GB', available: '900 GB', status: 'active' },
|
||||
{ id: '3', name: 'nfs-backup', type: 'nfs', remote: 'nfs', node: 'pve2', used: '200 GB', total: '2000 GB', available: '1800 GB', status: 'active' },
|
||||
];
|
||||
const [clusters, setClusters] = useState<ClusterInfo[]>([]);
|
||||
const [selectedClusterId, setSelectedClusterId] = useState<string>('');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [storages, setStorages] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
listProxmoxClusters()
|
||||
.then((cls) => {
|
||||
setClusters(cls);
|
||||
if (cls.length > 0) setSelectedClusterId(cls[0].id);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to load clusters:', err);
|
||||
toast.error('Failed to load clusters');
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadStorages = useCallback(async (clusterId: string) => {
|
||||
if (!clusterId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await listProxmoxDatastores(clusterId);
|
||||
setStorages(data);
|
||||
} catch (err) {
|
||||
console.error('Failed to load storages:', err);
|
||||
toast.error('Failed to load storages');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedClusterId) loadStorages(selectedClusterId);
|
||||
}, [selectedClusterId, loadStorages]);
|
||||
|
||||
if (clusters.length === 0 && !isLoading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Storage</h1>
|
||||
<p className="text-muted-foreground">Manage storage pools and volumes</p>
|
||||
</div>
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<p>No Proxmox clusters configured.</p>
|
||||
<p className="text-sm mt-1">Add a remote connection first.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -17,8 +65,19 @@ export function ProxmoxStoragePage() {
|
||||
<h1 className="text-2xl font-bold">Storage</h1>
|
||||
<p className="text-muted-foreground">Manage storage pools and volumes</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
{clusters.length > 1 && (
|
||||
<select
|
||||
className="rounded-md border px-3 py-1.5 text-sm bg-background"
|
||||
value={selectedClusterId}
|
||||
onChange={(e) => setSelectedClusterId(e.target.value)}
|
||||
>
|
||||
{clusters.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
<Button variant="outline" size="sm" onClick={() => loadStorages(selectedClusterId)}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
@ -27,7 +86,7 @@ export function ProxmoxStoragePage() {
|
||||
|
||||
<StorageList
|
||||
storages={storages}
|
||||
onRefresh={() => {}}
|
||||
onRefresh={() => loadStorages(selectedClusterId)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,28 +1,63 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
import { VMList } from '@/components/Proxmox';
|
||||
|
||||
interface VMInfo {
|
||||
id: string;
|
||||
vmid: number;
|
||||
name: string;
|
||||
node: string;
|
||||
status: 'running' | 'stopped' | 'paused';
|
||||
cpu: number;
|
||||
memory: number;
|
||||
memoryTotal: number;
|
||||
disk: number;
|
||||
diskTotal: number;
|
||||
uptime?: string;
|
||||
}
|
||||
import { listProxmoxClusters, listProxmoxVms } from '@/lib/proxmoxClient';
|
||||
import type { ClusterInfo } from '@/lib/domain';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function ProxmoxVMsPage() {
|
||||
const vms: VMInfo[] = [
|
||||
{ id: '1', name: 'web-server-01', vmid: 100, node: 'pve1', status: 'running', cpu: 4, memory: 8192, memoryTotal: 8192, disk: 100, diskTotal: 100, uptime: '2d 4h' },
|
||||
{ id: '2', name: 'db-server-01', vmid: 101, node: 'pve2', status: 'running', cpu: 8, memory: 16384, memoryTotal: 16384, disk: 500, diskTotal: 500, uptime: '5d 12h' },
|
||||
{ id: '3', name: 'dev-vm', vmid: 102, node: 'pve1', status: 'stopped', cpu: 2, memory: 4096, memoryTotal: 4096, disk: 50, diskTotal: 50 },
|
||||
];
|
||||
const [clusters, setClusters] = useState<ClusterInfo[]>([]);
|
||||
const [selectedClusterId, setSelectedClusterId] = useState<string>('');
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [vms, setVms] = useState<any[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [selectedVMs, setSelectedVMs] = useState<Set<string>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
listProxmoxClusters()
|
||||
.then((cls) => {
|
||||
setClusters(cls);
|
||||
if (cls.length > 0) setSelectedClusterId(cls[0].id);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to load clusters:', err);
|
||||
toast.error('Failed to load clusters');
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loadVms = useCallback(async (clusterId: string) => {
|
||||
if (!clusterId) return;
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const data = await listProxmoxVms(clusterId);
|
||||
setVms(data);
|
||||
} catch (err) {
|
||||
console.error('Failed to load VMs:', err);
|
||||
toast.error('Failed to load VMs');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedClusterId) loadVms(selectedClusterId);
|
||||
}, [selectedClusterId, loadVms]);
|
||||
|
||||
if (clusters.length === 0 && !isLoading) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Virtual Machines</h1>
|
||||
<p className="text-muted-foreground">Manage QEMU/KVM virtual machines</p>
|
||||
</div>
|
||||
<div className="text-center py-12 text-muted-foreground">
|
||||
<p>No Proxmox clusters configured.</p>
|
||||
<p className="text-sm mt-1">Add a remote connection first.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@ -31,8 +66,19 @@ export function ProxmoxVMsPage() {
|
||||
<h1 className="text-2xl font-bold">Virtual Machines</h1>
|
||||
<p className="text-muted-foreground">Manage QEMU/KVM virtual machines</p>
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Button variant="outline" size="sm">
|
||||
<div className="flex items-center space-x-2">
|
||||
{clusters.length > 1 && (
|
||||
<select
|
||||
className="rounded-md border px-3 py-1.5 text-sm bg-background"
|
||||
value={selectedClusterId}
|
||||
onChange={(e) => setSelectedClusterId(e.target.value)}
|
||||
>
|
||||
{clusters.map((c) => (
|
||||
<option key={c.id} value={c.id}>{c.name}</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
<Button variant="outline" size="sm" onClick={() => loadVms(selectedClusterId)}>
|
||||
<RefreshCw className="mr-2 h-4 w-4" />
|
||||
Refresh
|
||||
</Button>
|
||||
@ -41,25 +87,20 @@ export function ProxmoxVMsPage() {
|
||||
|
||||
<VMList
|
||||
vms={vms}
|
||||
onRefresh={() => {}}
|
||||
onVMAction={(_vm, _action) => {
|
||||
// VM action handler
|
||||
}}
|
||||
onSnapshotAction={(_vm, _action) => {
|
||||
// Snapshot action handler
|
||||
}}
|
||||
onMigrate={(_vm) => {
|
||||
// Migrate handler
|
||||
}}
|
||||
onClone={(_vm) => {
|
||||
// Clone handler
|
||||
}}
|
||||
onDelete={(_vm) => {
|
||||
// Delete handler
|
||||
}}
|
||||
selectedVMs={new Set()}
|
||||
onToggleSelect={(_vm) => {
|
||||
// VM select handler
|
||||
onRefresh={() => loadVms(selectedClusterId)}
|
||||
onVMAction={(_vm, _action) => { toast.info('VM action — not yet implemented'); }}
|
||||
onSnapshotAction={(_vm, _action) => { toast.info('Snapshot action — not yet implemented'); }}
|
||||
onMigrate={(_vm) => { toast.info('Migrate — not yet implemented'); }}
|
||||
onClone={(_vm) => { toast.info('Clone — not yet implemented'); }}
|
||||
onDelete={(_vm) => { toast.info('Delete — not yet implemented'); }}
|
||||
selectedVMs={selectedVMs}
|
||||
onToggleSelect={(vm) => {
|
||||
setSelectedVMs((prev) => {
|
||||
const next = new Set(prev);
|
||||
const id = String(vm.vmid);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/index';
|
||||
import { Button } from '@/components/ui/index';
|
||||
import { RefreshCw, Check, AlertCircle, Loader } from 'lucide-react';
|
||||
import { RefreshCw, Check, AlertCircle, Loader, ExternalLink } from 'lucide-react';
|
||||
import {
|
||||
checkAppUpdatesCmd,
|
||||
installAppUpdatesCmd,
|
||||
getUpdateChannelCmd,
|
||||
setUpdateChannelCmd,
|
||||
type UpdateCheckResult,
|
||||
} from '@/lib/tauriCommands';
|
||||
|
||||
export function Updater() {
|
||||
const [channel, setChannel] = useState('stable');
|
||||
const [checking, setChecking] = useState(false);
|
||||
const [updateAvailable, setUpdateAvailable] = useState(false);
|
||||
const [result, setResult] = useState<UpdateCheckResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const loadChannel = async () => {
|
||||
@ -28,21 +29,20 @@ export function Updater() {
|
||||
setChecking(true);
|
||||
setError(null);
|
||||
try {
|
||||
const available = await checkAppUpdatesCmd();
|
||||
setUpdateAvailable(available);
|
||||
} catch {
|
||||
setError('Failed to check for updates');
|
||||
const data = await checkAppUpdatesCmd();
|
||||
setResult(data);
|
||||
} catch (err) {
|
||||
setError(String(err));
|
||||
} finally {
|
||||
setChecking(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstallUpdate = async () => {
|
||||
const handleDownloadUpdate = async () => {
|
||||
try {
|
||||
await installAppUpdatesCmd();
|
||||
setUpdateAvailable(false);
|
||||
} catch {
|
||||
setError('Failed to install update');
|
||||
} catch (err) {
|
||||
setError('Failed to open releases page: ' + String(err));
|
||||
}
|
||||
};
|
||||
|
||||
@ -64,7 +64,7 @@ export function Updater() {
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold">Updater</h1>
|
||||
<p className="text-muted-foreground">Configure application auto-updates</p>
|
||||
<p className="text-muted-foreground">Configure application updates</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
@ -121,48 +121,62 @@ export function Updater() {
|
||||
)}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardContent className="space-y-4">
|
||||
{error && (
|
||||
<div className="mb-4 flex items-center space-x-2 rounded-lg bg-destructive/15 p-3 text-destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<div className="flex items-center space-x-2 rounded-lg bg-destructive/15 p-3 text-destructive">
|
||||
<AlertCircle className="h-4 w-4 flex-shrink-0" />
|
||||
<span className="text-sm">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{updateAvailable ? (
|
||||
<div className="flex items-center justify-between rounded-lg bg-green-50 p-4 dark:bg-green-900/20">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="rounded-full bg-green-600 p-1 text-white">
|
||||
<Check className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-green-900 dark:text-green-100">
|
||||
Update Available
|
||||
</div>
|
||||
<div className="text-sm text-green-700 dark:text-green-300">
|
||||
A new version is ready to install
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={handleInstallUpdate}>
|
||||
Install Update
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between rounded-lg bg-muted p-4">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="rounded-full bg-muted-foreground p-1 text-background">
|
||||
<Check className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold">Up to Date</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
You are running the latest version
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{result && (
|
||||
<div className="text-sm text-muted-foreground space-y-1">
|
||||
<div>Current version: <span className="font-mono font-medium text-foreground">{result.currentVersion}</span></div>
|
||||
<div>Latest version: <span className="font-mono font-medium text-foreground">{result.latestVersion || '—'}</span></div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result?.updateAvailable ? (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between rounded-lg bg-green-50 p-4 dark:bg-green-900/20">
|
||||
<div className="flex items-center space-x-3">
|
||||
<div className="rounded-full bg-green-600 p-1 text-white">
|
||||
<Check className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold text-green-900 dark:text-green-100">
|
||||
Update Available — v{result.latestVersion}
|
||||
</div>
|
||||
<div className="text-sm text-green-700 dark:text-green-300">
|
||||
Click below to open the releases page and download
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={handleDownloadUpdate}>
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
Download Update
|
||||
</Button>
|
||||
</div>
|
||||
{result.releaseNotes && (
|
||||
<div className="rounded-lg border p-3 text-sm">
|
||||
<div className="font-medium mb-1">Release Notes</div>
|
||||
<pre className="whitespace-pre-wrap text-muted-foreground font-sans">{result.releaseNotes}</pre>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : result ? (
|
||||
<div className="flex items-center space-x-3 rounded-lg bg-muted p-4">
|
||||
<div className="rounded-full bg-muted-foreground p-1 text-background">
|
||||
<Check className="h-4 w-4" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-semibold">Up to Date</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
You are running the latest version
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user