From 914c9fbac82132412157984cfff6b0c057a50792 Mon Sep 17 00:00:00 2001 From: Maaz Khokhar Date: Sat, 27 Jun 2026 20:31:52 -0500 Subject: [PATCH] icmp pinging + deleting --- components/app-card.tsx | 70 +++++++++---- components/delete-dialog.tsx | 25 ++++- src-tauri/Cargo.lock | 186 ++++++++++++++++++++++++++++++++--- src-tauri/Cargo.toml | 2 + src-tauri/src/db/mod.rs | 8 ++ src-tauri/src/lib.rs | 68 ++++++++++++- 6 files changed, 323 insertions(+), 36 deletions(-) diff --git a/components/app-card.tsx b/components/app-card.tsx index 3796a63..e92dc15 100644 --- a/components/app-card.tsx +++ b/components/app-card.tsx @@ -1,8 +1,9 @@ "use client"; import { motion, AnimatePresence } from "motion/react"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import ActionMenu from "./action-menu"; +import { invoke } from "@tauri-apps/api/core"; type App = { id: string; @@ -16,11 +17,53 @@ interface Props { app: App; } -export default function AppCard({ app }: Props) { - let pingStatus = true; +type PingStatus = "success" | "error" | "loading"; +export default function AppCard({ app }: Props) { + const [pingStatus, setPingStatus] = useState("loading"); const [hovered, setHovered] = useState(false); + let pingColor = + pingStatus === "success" + ? "bg-green-500" + : pingStatus === "error" + ? "bg-red-500" + : "bg-gray-500"; + + const pingApp = async (): Promise => { + let status: boolean = await invoke("ping", { address: app.address }); + return status; + }; + + useEffect(() => { + let isMounted = true; + + const performPing = async () => { + setPingStatus("loading"); + try { + // Since we simplified the Rust command to return a bool, we just read the result + const success = await invoke("ping", { address: app.address }); + if (isMounted) { + setPingStatus(success ? "success" : "error"); + } + } catch (err) { + if (isMounted) { + setPingStatus("error"); + } + } + }; + + performPing(); + + // Optional: Set up an interval if you want it to poll periodically + const interval = setInterval(performPing, 10000); // Poll every 10 seconds + + return () => { + isMounted = false; + clearInterval(interval); + }; + }, [app.address]); + return (
setHovered(true)} @@ -37,8 +80,12 @@ export default function AppCard({ app }: Props) { >
-
-
+
+
@@ -50,19 +97,6 @@ export default function AppCard({ app }: Props) { > {app.address} - {/* - {hovered && ( - - {app.description && {app.description}} - - - )} - */} {hovered && ( diff --git a/components/delete-dialog.tsx b/components/delete-dialog.tsx index 5c819e7..5952190 100644 --- a/components/delete-dialog.tsx +++ b/components/delete-dialog.tsx @@ -12,6 +12,9 @@ import { DialogHeader, DialogTitle, } from "@/components/ui/dialog"; +import { invoke } from "@tauri-apps/api/core"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { toast } from "sonner"; type DeleteDialogProps = { id: string; @@ -25,11 +28,29 @@ export default function DeleteDialog({ onOpenChange, }: DeleteDialogProps) { const formRef = useRef(null); + const queryClient = useQueryClient(); + + const delete_app = async (id: string) => { + await invoke("delete_apps", { id }); + }; + + const { mutate: deleteApp } = useMutation({ + mutationKey: ["delete_app"], + mutationFn: delete_app, + onSuccess: () => { + console.log("App deleted successfully"); + queryClient.invalidateQueries({ queryKey: ["apps"] }); + }, + onError: (error) => { + toast.error("Error deleting app"); + console.error("Error deleting app:", error); + }, + }); const handleDelete = async (e: React.FormEvent) => { e.preventDefault(); - console.log(`Deleting app with id: ${id}`); - onOpenChange(false); // Close dialog after deleting + deleteApp(id); + onOpenChange(false); }; const handleHoldComplete = () => { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 18e7263..ce4b22a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -53,11 +53,13 @@ version = "0.1.0" dependencies = [ "diesel", "dotenvy", + "ping-async", "serde", "serde_json", "tauri", "tauri-build", "tauri-plugin-opener", + "tokio", "uuid", ] @@ -1134,6 +1136,21 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1141,6 +1158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1208,6 +1226,7 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -2553,6 +2572,20 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "ping-async" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824acfdc2bb9cc441daa997c034bc2596db93adb5b754b386f9a658c799e7b20" +dependencies = [ + "futures", + "rand", + "socket2", + "static_assertions", + "tokio", + "windows 0.62.2", +] + [[package]] name = "piper" version = "0.2.5" @@ -2638,6 +2671,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + [[package]] name = "precomputed-hash" version = "0.1.1" @@ -2736,6 +2778,35 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "raw-window-handle" version = "0.6.2" @@ -3269,6 +3340,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "string_cache" version = "0.9.0" @@ -3398,7 +3475,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -3469,7 +3546,7 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows", + "windows 0.61.3", ] [[package]] @@ -3568,7 +3645,7 @@ dependencies = [ "tauri-plugin", "thiserror 2.0.18", "url", - "windows", + "windows 0.61.3", "zbus", ] @@ -3594,7 +3671,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", ] [[package]] @@ -3619,7 +3696,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows", + "windows 0.61.3", "wry", ] @@ -3799,11 +3876,25 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4380,7 +4471,7 @@ checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-implement", "windows-interface", @@ -4404,7 +4495,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ "thiserror 2.0.18", - "windows", + "windows 0.61.3", "windows-core 0.61.2", ] @@ -4460,11 +4551,23 @@ version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-collections", + "windows-collections 0.2.0", "windows-core 0.61.2", - "windows-future", + "windows-future 0.2.1", "windows-link 0.1.3", - "windows-numerics", + "windows-numerics 0.2.0", +] + +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] @@ -4476,6 +4579,15 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.61.2" @@ -4510,7 +4622,18 @@ checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", "windows-link 0.1.3", - "windows-threading", + "windows-threading 0.1.0", +] + +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] @@ -4557,6 +4680,16 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -4660,6 +4793,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-version" version = "0.1.7" @@ -4843,7 +4985,7 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", + "windows 0.61.3", "windows-core 0.61.2", "windows-version", "x11-dl", @@ -4954,6 +5096,26 @@ dependencies = [ "zvariant", ] +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "zerofrom" version = "0.1.8" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 85ef0ee..c96584e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -28,3 +28,5 @@ diesel = { version = "2.3.10", features = [ ] } dotenvy = "0.15.7" uuid = { version = "1.23.4", features = ["v4"] } +tokio = { version = "1.52.3", features = ["full"] } +ping-async = "1.0.2" diff --git a/src-tauri/src/db/mod.rs b/src-tauri/src/db/mod.rs index 8d95636..f642b96 100644 --- a/src-tauri/src/db/mod.rs +++ b/src-tauri/src/db/mod.rs @@ -45,3 +45,11 @@ pub fn get_apps(conn: &mut SqliteConnection) -> Vec { .load::(conn) .expect("Error loading apps") } + +pub fn delete_app(conn: &mut SqliteConnection, app_id: &str) -> usize { + use crate::db::schema::app::dsl::*; + + diesel::delete(app.filter(id.eq(app_id))) + .execute(conn) + .expect("Error deleting app") +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 47f4747..7861d6b 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,8 +1,49 @@ +use crate::db::{create_app, delete_app, establish_connection, get_apps}; use db::models::App; - -use crate::db::{create_app, establish_connection, get_apps}; +use ping_async::IcmpEchoRequestor; +use std::net::IpAddr; +use std::time::Duration; +use tokio::net::lookup_host; mod db; +/// Pings a target (domain or IP) on a given port with a 5-second timeout. +/// Returns true if successful, false otherwise. +async fn ping_target(address: &str) -> bool { + // 1. Resolve host domain to an IP address (ICMP needs an explicit IP) + // If it's already an IP string (e.g. "192.168.1.10"), lookup_host still parses it fine if appended with a dummy port + let target_host = if address.contains(':') { + address.to_string() + } else { + format!("{}:0", address) + }; + + let Ok(mut addresses) = lookup_host(&target_host).await else { + return false; + }; + + let Some(socket_addr) = addresses.next() else { + return false; + }; + + let ip_target: IpAddr = socket_addr.ip(); + + // 2. Configure unprivileged ICMP requestor + let timeout = Some(Duration::from_secs(2)); + let pinger = match IcmpEchoRequestor::new(ip_target, None, None, timeout) { + Ok(p) => p, + Err(_) => return false, + }; + + // 3. Send ICMP Echo request asynchronously + match pinger.send().await { + Ok(reply) => { + // Check if the status returned is a valid success response + matches!(reply.status(), ping_async::IcmpEchoStatus::Success) + } + Err(_) => false, + } +} + #[tauri::command] fn new_app(name: &str, address: &str, description: Option<&str>, ssh_address: Option<&str>) -> App { let mut conn = &mut establish_connection(); @@ -13,7 +54,7 @@ fn new_app(name: &str, address: &str, description: Option<&str>, ssh_address: Op } #[tauri::command] -async fn get_app() -> Vec { +fn get_app() -> Vec { let mut conn = &mut establish_connection(); let apps = get_apps(&mut conn); @@ -21,11 +62,30 @@ async fn get_app() -> Vec { apps } +#[tauri::command] +fn delete_apps(id: &str) { + let conn = &mut establish_connection(); + delete_app(conn, id); +} + +#[tauri::command] +async fn ping(address: &str) -> Result { + match ping_target(address).await { + true => Ok(true), + false => Err(format!("Failed to ping {}", address)), + } +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) - .invoke_handler(tauri::generate_handler![new_app, get_app]) + .invoke_handler(tauri::generate_handler![ + new_app, + get_app, + delete_apps, + ping + ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); }