diff --git a/webui/web/css/main.css b/webui/web/css/main.css
index 4c5d0ab..1e2627c 100644
--- a/webui/web/css/main.css
+++ b/webui/web/css/main.css
@@ -1093,6 +1093,70 @@ body {
display: none;
}
+/* ===== MODAL ===== */
+.modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.7);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1001;
+ opacity: 0;
+ transition: opacity var(--transition-base);
+ padding: var(--space-4);
+}
+
+.modal-overlay.show {
+ opacity: 1;
+}
+
+.modal-overlay.hidden {
+ display: none;
+}
+
+.modal {
+ background: var(--bg-elevated);
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+ padding: var(--space-8);
+ max-width: 400px;
+ width: 100%;
+ box-shadow: var(--shadow-lg);
+ transform: scale(0.95);
+ transition: transform var(--transition-base);
+ text-align: center;
+}
+
+.modal-overlay.show .modal {
+ transform: scale(1);
+}
+
+.modal-title {
+ font-size: var(--text-xl);
+ font-weight: 700;
+ color: var(--error);
+ margin-bottom: var(--space-4);
+}
+
+.modal-message {
+ font-size: var(--text-base);
+ color: var(--text-secondary);
+ margin-bottom: var(--space-8);
+ line-height: 1.6;
+}
+
+.modal-actions {
+ display: flex;
+ gap: var(--space-3);
+}
+
+.modal-actions .btn {
+ flex: 1;
+ padding: var(--space-4);
+}
+
/* ===== ANIMATIONS ===== */
@keyframes fadeIn {
from {
diff --git a/webui/web/index.html b/webui/web/index.html
index 21d573d..53ae618 100644
--- a/webui/web/index.html
+++ b/webui/web/index.html
@@ -643,6 +643,9 @@
+
+
+
diff --git a/webui/web/js/api/probe.js b/webui/web/js/api/probe.js
new file mode 100644
index 0000000..dc72129
--- /dev/null
+++ b/webui/web/js/api/probe.js
@@ -0,0 +1,24 @@
+export class ProbeAPI {
+ constructor(baseURL = '') {
+ this.baseURL = baseURL;
+ }
+
+ /**
+ * Probe a device at the given IP address.
+ * Returns device info: reachable status, vendor, hostname, mDNS data.
+ * @param {string} ip - IP address to probe
+ * @returns {Promise