[object Object]

← back to Port Viewer

initial scaffold (gitify-all 2026-05-06)

363d68c11237b8119f5311e7d8de58fe8db2a727 · 2026-05-06 10:25:45 -0700 · Steve Abrams

Files touched

Diff

commit 363d68c11237b8119f5311e7d8de58fe8db2a727
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 10:25:45 2026 -0700

    initial scaffold (gitify-all 2026-05-06)
---
 .claude/settings.local.json |   39 +
 .gitignore                  |   12 +
 package-lock.json           |  842 ++++++++++++
 package.json                |   13 +
 public/favicon.svg          |    4 +
 public/index.html           | 2982 +++++++++++++++++++++++++++++++++++++++++++
 server.js                   |  972 ++++++++++++++
 test.html                   |   14 +
 8 files changed, 4878 insertions(+)

diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..45b679c
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,39 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(lsof:*)",
+      "Bash(node server.js)",
+      "Bash(curl:*)",
+      "Bash(ss:*)",
+      "Read(//etc/nginx/sites-enabled/**)",
+      "Read(//etc/nginx/sites-available/**)",
+      "Bash(nginx:*)",
+      "Bash(systemctl reload:*)",
+      "Bash(for i in {1..10})",
+      "Bash(do echo \"Test $i:\")",
+      "Bash(done)",
+      "Bash(systemctl list-units:*)",
+      "Bash(node:*)",
+      "Read(//tmp/**)",
+      "Bash(cat:*)",
+      "Bash(npm install:*)",
+      "Read(//root/**)",
+      "Bash(chmod:*)",
+      "Bash(/root/fix-all-ports.sh:*)",
+      "Bash(systemctl stop:*)",
+      "Bash(systemctl status:*)",
+      "Bash(xargs kill:*)",
+      "Bash(ufw:*)",
+      "Bash(iptables:*)",
+      "Bash(systemctl:*)",
+      "Bash(systemctl restart:*)",
+      "Bash(/root/port-viewer-watchdog.sh:*)",
+      "Bash(npm run build:*)",
+      "Bash(pkill:*)",
+      "Bash(bash -c:*)",
+      "Read(//opt/**)"
+    ],
+    "deny": [],
+    "ask": []
+  }
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7e6a9c3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+node_modules/
+.env
+.env.local
+.env.*.local
+.env.*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+*.bak
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..a87a309
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,842 @@
+{
+  "name": "port-viewer",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "port-viewer",
+      "version": "1.0.0",
+      "dependencies": {
+        "express": "^4.18.2",
+        "nodemailer": "^7.0.6"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "license": "MIT"
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.13.0",
+        "raw-body": "2.5.2",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+      "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.21.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+      "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.3",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.7.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.3.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.3",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.12",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.13.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.19.0",
+        "serve-static": "1.16.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/nodemailer": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.6.tgz",
+      "integrity": "sha512-F44uVzgwo49xboqbFgBGkRaiMgtoBrBEWCVincJPK9+S9Adkzt/wXCLKbf7dxucmxfTI5gHGB+bEmdyzN6QKjw==",
+      "license": "MIT-0",
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+      "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+      "license": "MIT"
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.0.6"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/send": {
+      "version": "0.19.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.19.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/side-channel": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3d7ac90
--- /dev/null
+++ b/package.json
@@ -0,0 +1,13 @@
+{
+  "name": "port-viewer",
+  "version": "1.0.0",
+  "description": "A beautiful web app to view active network ports",
+  "main": "server.js",
+  "scripts": {
+    "start": "node server.js"
+  },
+  "dependencies": {
+    "express": "^4.18.2",
+    "nodemailer": "^7.0.6"
+  }
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..1d9f2c8
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+<rect width="32" height="32" rx="6" fill="#10b981"/>
+<text x="50%" y="55%" text-anchor="middle" dominant-baseline="middle" font-size="20" font-family="Apple Color Emoji, Segoe UI Emoji, sans-serif" fill="white">P</text>
+</svg>
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..5e3d817
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,2982 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Port Viewer - Active Ports</title>
+    <style>
+        * {
+            margin: 0;
+            padding: 0;
+            box-sizing: border-box;
+        }
+
+        body {
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            min-height: 100vh;
+            padding: 20px;
+        }
+
+        .container {
+            max-width: 1400px;
+            margin: 0 auto;
+        }
+
+        header {
+            text-align: center;
+            color: white;
+            margin-bottom: 40px;
+            animation: fadeIn 0.6s ease-in;
+        }
+
+        h1 {
+            font-size: 3em;
+            margin-bottom: 10px;
+            text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
+        }
+
+        .subtitle {
+            font-size: 1.2em;
+            opacity: 0.9;
+        }
+
+        .stats {
+            display: flex;
+            justify-content: center;
+            gap: 30px;
+            margin: 20px 0;
+            flex-wrap: wrap;
+        }
+
+        .stat-box {
+            background: rgba(255,255,255,0.2);
+            backdrop-filter: blur(10px);
+            padding: 15px 30px;
+            border-radius: 15px;
+            color: white;
+            font-weight: bold;
+        }
+
+        .stat-number {
+            font-size: 2em;
+            display: block;
+        }
+
+        .cards-grid {
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+            gap: 25px;
+            animation: fadeIn 0.8s ease-in;
+        }
+
+        .card {
+            background: white;
+            border-radius: 20px;
+            padding: 25px;
+            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
+            transition: transform 0.3s ease, box-shadow 0.3s ease;
+            position: relative;
+            overflow: hidden;
+            animation: slideUp 0.5s ease-out;
+        }
+
+        .card::before {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            height: 5px;
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+        }
+
+        .card.active-user-app {
+            border: 3px solid #00b894;
+            box-shadow: 0 10px 30px rgba(0, 184, 148, 0.3);
+        }
+
+        .card.active-user-app::before {
+            background: linear-gradient(90deg, #00b894 0%, #00a085 100%);
+            height: 8px;
+        }
+
+        .card.offline-app {
+            border: 3px solid #d63031;
+            background: #ffe5e5;
+            box-shadow: 0 10px 30px rgba(214, 48, 49, 0.3);
+        }
+
+        .card.offline-app::before {
+            background: linear-gradient(90deg, #d63031 0%, #c0392b 100%);
+            height: 8px;
+        }
+
+        .card.loading {
+            border: 3px solid #0984e3;
+            background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
+            box-shadow: 0 10px 30px rgba(9, 132, 227, 0.3);
+            position: relative;
+        }
+
+        .loading-spinner {
+            display: inline-block;
+            width: 16px;
+            height: 16px;
+            border: 2px solid rgba(255, 255, 255, 0.3);
+            border-radius: 50%;
+            border-top-color: #fff;
+            animation: spin 0.8s linear infinite;
+            margin-left: 8px;
+            vertical-align: middle;
+        }
+
+        .loading-overlay {
+            position: absolute;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgba(9, 132, 227, 0.15);
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-direction: column;
+            gap: 15px;
+            border-radius: 20px;
+            z-index: 10;
+        }
+
+        .loading-overlay .big-spinner {
+            width: 60px;
+            height: 60px;
+            border: 5px solid rgba(9, 132, 227, 0.2);
+            border-radius: 50%;
+            border-top-color: #0984e3;
+            animation: spin 1s linear infinite;
+        }
+
+        .loading-text {
+            color: #0984e3;
+            font-weight: 600;
+            font-size: 1.2em;
+            text-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        }
+
+        @keyframes spin {
+            to { transform: rotate(360deg); }
+        }
+
+        .card:hover {
+            transform: translateY(-10px);
+            box-shadow: 0 15px 40px rgba(0,0,0,0.3);
+        }
+
+        .port-number {
+            font-size: 3em;
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 10px;
+        }
+
+        .port-info {
+            margin: 10px 0;
+        }
+
+        .info-label {
+            font-size: 0.9em;
+            color: #666;
+            text-transform: uppercase;
+            letter-spacing: 1px;
+            margin-bottom: 5px;
+        }
+
+        .info-value {
+            font-size: 1.1em;
+            color: #333;
+            font-weight: 600;
+            word-break: break-all;
+        }
+
+        .protocol-badge {
+            display: inline-block;
+            padding: 5px 15px;
+            border-radius: 20px;
+            font-size: 0.9em;
+            font-weight: bold;
+            margin-top: 10px;
+        }
+
+        .protocol-tcp {
+            background: #e3f2fd;
+            color: #1976d2;
+        }
+
+        .protocol-tcp6 {
+            background: #f3e5f5;
+            color: #7b1fa2;
+        }
+
+        .protocol-udp {
+            background: #e8f5e9;
+            color: #388e3c;
+        }
+
+        .loading {
+            text-align: center;
+            color: white;
+            font-size: 1.5em;
+            margin-top: 50px;
+        }
+
+        .spinner {
+            border: 4px solid rgba(255,255,255,0.3);
+            border-top: 4px solid white;
+            border-radius: 50%;
+            width: 50px;
+            height: 50px;
+            animation: spin 1s linear infinite;
+            margin: 20px auto;
+        }
+
+        .refresh-btn {
+            position: fixed;
+            bottom: 30px;
+            right: 30px;
+            background: white;
+            color: #667eea;
+            border: none;
+            padding: 15px 30px;
+            border-radius: 50px;
+            font-size: 1em;
+            font-weight: bold;
+            cursor: pointer;
+            box-shadow: 0 5px 20px rgba(0,0,0,0.3);
+            transition: all 0.3s ease;
+        }
+
+        .refresh-btn:hover {
+            transform: scale(1.1);
+            box-shadow: 0 8px 30px rgba(0,0,0,0.4);
+        }
+
+        @keyframes fadeIn {
+            from { opacity: 0; }
+            to { opacity: 1; }
+        }
+
+        @keyframes slideUp {
+            from {
+                opacity: 0;
+                transform: translateY(30px);
+            }
+            to {
+                opacity: 1;
+                transform: translateY(0);
+            }
+        }
+
+        @keyframes spin {
+            0% { transform: rotate(0deg); }
+            100% { transform: rotate(360deg); }
+        }
+
+        .error {
+            background: rgba(255,255,255,0.9);
+            color: #d32f2f;
+            padding: 20px;
+            border-radius: 15px;
+            text-align: center;
+            margin: 20px;
+        }
+
+        .app-name {
+            font-size: 1.3em;
+            font-weight: bold;
+            color: #764ba2;
+            margin: 15px 0;
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }
+
+        .website-links {
+            margin: 10px 0;
+        }
+
+        .website-link {
+            display: inline-block;
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            padding: 8px 16px;
+            border-radius: 20px;
+            text-decoration: none;
+            font-size: 0.9em;
+            margin: 5px 5px 5px 0;
+            transition: all 0.3s ease;
+            position: relative;
+        }
+
+        .website-link:hover {
+            transform: scale(1.05);
+            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+        }
+
+        .web-view-container {
+            margin-top: 15px;
+            border-top: 2px solid #e0e0e0;
+            padding-top: 15px;
+        }
+
+        .web-view-toggle {
+            background: linear-gradient(90deg, #00b894 0%, #00a085 100%);
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 20px;
+            cursor: pointer;
+            font-weight: bold;
+            margin-bottom: 10px;
+            width: 100%;
+            transition: all 0.3s ease;
+            font-size: 0.9em;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            gap: 8px;
+        }
+
+        .web-view-toggle:hover {
+            transform: scale(1.02);
+            box-shadow: 0 5px 15px rgba(0, 184, 148, 0.4);
+        }
+
+        .web-view-toggle.active {
+            background: linear-gradient(90deg, #ff6b6b 0%, #ee5a6f 100%);
+        }
+
+        .web-view-iframe-container {
+            display: block;
+            width: 100%;
+            height: 300px;
+            border-radius: 10px;
+            overflow: hidden;
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
+            margin-top: 10px;
+            position: relative;
+            background: white;
+            animation: slideDown 0.3s ease-out;
+        }
+
+        .web-view-iframe-container.active {
+            display: block;
+        }
+
+        @keyframes slideDown {
+            from {
+                opacity: 0;
+                max-height: 0;
+            }
+            to {
+                opacity: 1;
+                max-height: 400px;
+            }
+        }
+
+        .web-view-iframe {
+            width: 100%;
+            height: 100%;
+            border: none;
+            background: white;
+        }
+
+        .web-view-loading {
+            position: absolute;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            gap: 10px;
+            color: #667eea;
+        }
+
+        .web-view-error {
+            padding: 20px;
+            text-align: center;
+            color: #d63031;
+            font-weight: bold;
+        }
+
+        .fullscreen-btn {
+            position: absolute;
+            top: 10px;
+            right: 10px;
+            background: rgba(102, 126, 234, 0.9);
+            color: white;
+            border: none;
+            padding: 8px 12px;
+            border-radius: 8px;
+            cursor: pointer;
+            font-size: 0.85em;
+            font-weight: bold;
+            z-index: 10;
+            transition: all 0.3s ease;
+        }
+
+        .fullscreen-btn:hover {
+            background: rgba(102, 126, 234, 1);
+            transform: scale(1.05);
+        }
+
+        .preview-container {
+            position: fixed;
+            top: 50%;
+            left: 50%;
+            transform: translate(-50%, -50%);
+            width: 80%;
+            max-width: 1200px;
+            height: 80%;
+            background: white;
+            border-radius: 20px;
+            box-shadow: 0 20px 60px rgba(0,0,0,0.5);
+            z-index: 1000;
+            display: none;
+            flex-direction: column;
+            overflow: hidden;
+        }
+
+        .preview-container.active {
+            display: flex;
+        }
+
+        .preview-header {
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            padding: 20px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+        }
+
+        .preview-title {
+            font-size: 1.5em;
+            font-weight: bold;
+        }
+
+        .close-preview {
+            background: rgba(255,255,255,0.3);
+            border: none;
+            color: white;
+            font-size: 1.5em;
+            width: 40px;
+            height: 40px;
+            border-radius: 50%;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .close-preview:hover {
+            background: rgba(255,255,255,0.5);
+            transform: rotate(90deg);
+        }
+
+        .preview-iframe {
+            flex: 1;
+            border: none;
+            background: white;
+        }
+
+        .preview-overlay {
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: rgba(0,0,0,0.7);
+            z-index: 999;
+            display: none;
+        }
+
+        .preview-overlay.active {
+            display: block;
+        }
+
+        .docker-badge {
+            background: #e3f2fd;
+            color: #1976d2;
+            padding: 5px 12px;
+            border-radius: 15px;
+            font-size: 0.8em;
+            display: inline-block;
+            margin-top: 5px;
+        }
+
+        .open-icon {
+            font-size: 0.8em;
+            opacity: 0.8;
+        }
+
+        .directory-info {
+            background: #f5f5f5;
+            padding: 10px;
+            border-radius: 10px;
+            margin: 10px 0;
+            font-size: 0.9em;
+            color: #666;
+            font-family: monospace;
+            word-break: break-all;
+        }
+
+        .restart-btn {
+            background: linear-gradient(90deg, #ff6b6b 0%, #ee5a6f 100%);
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 20px;
+            cursor: pointer;
+            font-weight: bold;
+            margin-top: 10px;
+            width: 100%;
+            transition: all 0.3s ease;
+            font-size: 0.9em;
+        }
+
+        .restart-btn:hover {
+            transform: scale(1.05);
+            box-shadow: 0 5px 15px rgba(238, 90, 111, 0.4);
+        }
+
+        .restart-btn:active {
+            transform: scale(0.95);
+        }
+
+        .restart-btn:disabled {
+            background: #ccc;
+            cursor: not-allowed;
+        }
+
+        .system-badge {
+            background: #ffeaa7;
+            color: #d63031;
+            padding: 5px 12px;
+            border-radius: 15px;
+            font-size: 0.8em;
+            display: inline-block;
+            margin-top: 5px;
+        }
+
+        .user-app-indicator {
+            background: #55efc4;
+            color: #00b894;
+            padding: 5px 12px;
+            border-radius: 15px;
+            font-size: 0.8em;
+            display: inline-block;
+            margin-top: 5px;
+        }
+
+        .last-port-info {
+            background: #dfe6e9;
+            padding: 8px 12px;
+            border-radius: 10px;
+            margin: 5px 0;
+            font-size: 0.85em;
+            color: #2d3436;
+        }
+
+        .offline-badge {
+            background: #d63031;
+            color: white;
+            padding: 8px 15px;
+            border-radius: 15px;
+            font-size: 0.9em;
+            display: inline-block;
+            margin: 10px 0;
+            font-weight: bold;
+        }
+
+        .offline-app .port-number {
+            color: #d63031;
+            opacity: 0.7;
+        }
+
+        .offline-app .info-value,
+        .offline-app .info-label {
+            opacity: 0.6;
+        }
+
+        .active-indicator {
+            display: inline-block;
+            width: 12px;
+            height: 12px;
+            border-radius: 50%;
+            background: #00b894;
+            margin-left: 10px;
+            animation: pulse 2s infinite;
+        }
+
+        @keyframes pulse {
+            0%, 100% { opacity: 1; }
+            50% { opacity: 0.5; }
+        }
+
+        .add-dir-btn {
+            position: fixed;
+            bottom: 100px;
+            right: 30px;
+            background: linear-gradient(90deg, #00b894 0%, #00a085 100%);
+            color: white;
+            border: none;
+            padding: 15px 30px;
+            border-radius: 50px;
+            font-size: 1em;
+            font-weight: bold;
+            cursor: pointer;
+            box-shadow: 0 5px 20px rgba(0, 184, 148, 0.4);
+            transition: all 0.3s ease;
+        }
+
+        .add-dir-btn:hover {
+            transform: scale(1.1);
+            box-shadow: 0 8px 30px rgba(0, 184, 148, 0.5);
+        }
+
+        .modal {
+            display: none;
+            position: fixed;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background: rgba(0, 0, 0, 0.7);
+            z-index: 2000;
+            justify-content: center;
+            align-items: center;
+        }
+
+        .modal.active {
+            display: flex;
+        }
+
+        .modal-content {
+            background: white;
+            border-radius: 20px;
+            padding: 40px;
+            max-width: 500px;
+            width: 90%;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
+            animation: slideUp 0.3s ease-out;
+        }
+
+        .modal-header {
+            font-size: 2em;
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 20px;
+        }
+
+        .form-group {
+            margin-bottom: 20px;
+        }
+
+        .form-label {
+            display: block;
+            font-weight: bold;
+            color: #333;
+            margin-bottom: 8px;
+            font-size: 0.95em;
+        }
+
+        .form-input {
+            width: 100%;
+            padding: 12px;
+            border: 2px solid #ddd;
+            border-radius: 10px;
+            font-size: 1em;
+            transition: border-color 0.3s ease;
+        }
+
+        .form-input:focus {
+            outline: none;
+            border-color: #667eea;
+        }
+
+        .form-buttons {
+            display: flex;
+            gap: 10px;
+            margin-top: 30px;
+        }
+
+        .btn-submit {
+            flex: 1;
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 15px;
+            border-radius: 10px;
+            font-size: 1em;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .btn-submit:hover {
+            transform: scale(1.05);
+        }
+
+        .btn-cancel {
+            flex: 1;
+            background: #ddd;
+            color: #333;
+            border: none;
+            padding: 15px;
+            border-radius: 10px;
+            font-size: 1em;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .btn-cancel:hover {
+            background: #ccc;
+        }
+
+        .ask-server-section {
+            background: rgba(255, 255, 255, 0.95);
+            border-radius: 20px;
+            padding: 30px;
+            margin-bottom: 40px;
+            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+        }
+
+        .ask-header {
+            font-size: 1.8em;
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 10px;
+        }
+
+        .ask-subtitle {
+            color: #666;
+            margin-bottom: 20px;
+            font-size: 0.95em;
+        }
+
+        .ask-input-container {
+            display: flex;
+            gap: 10px;
+            margin-bottom: 20px;
+        }
+
+        .ask-input {
+            flex: 1;
+            padding: 15px 20px;
+            border: 2px solid #ddd;
+            border-radius: 50px;
+            font-size: 1em;
+            transition: border-color 0.3s ease;
+        }
+
+        .ask-input:focus {
+            outline: none;
+            border-color: #667eea;
+        }
+
+        .ask-btn {
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 15px 30px;
+            border-radius: 50px;
+            font-size: 1em;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            white-space: nowrap;
+        }
+
+        .ask-btn:hover {
+            transform: scale(1.05);
+            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+        }
+
+        .ask-btn:disabled {
+            background: #ccc;
+            cursor: not-allowed;
+            transform: none;
+        }
+
+        .ask-response {
+            background: #f8f9fa;
+            border-radius: 15px;
+            padding: 20px;
+            margin-top: 20px;
+            display: none;
+            animation: slideUp 0.3s ease-out;
+        }
+
+        .ask-response.show {
+            display: block;
+        }
+
+        .response-header {
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 10px;
+            font-size: 1.1em;
+        }
+
+        .response-content {
+            background: white;
+            padding: 15px;
+            border-radius: 10px;
+            font-family: 'Courier New', monospace;
+            font-size: 0.9em;
+            white-space: pre-wrap;
+            word-wrap: break-word;
+            max-height: 400px;
+            overflow-y: auto;
+            color: #333;
+        }
+
+        .response-error {
+            background: #ffe5e5;
+            color: #d63031;
+            padding: 15px;
+            border-radius: 10px;
+            font-weight: bold;
+        }
+
+        .quick-queries {
+            display: flex;
+            gap: 10px;
+            flex-wrap: wrap;
+            margin-top: 15px;
+        }
+
+        .quick-query-btn {
+            background: rgba(102, 126, 234, 0.1);
+            color: #667eea;
+            border: 2px solid #667eea;
+            padding: 8px 16px;
+            border-radius: 20px;
+            font-size: 0.85em;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .quick-query-btn:hover {
+            background: #667eea;
+            color: white;
+        }
+
+        .directories-section {
+            background: rgba(255, 255, 255, 0.95);
+            border-radius: 20px;
+            padding: 30px;
+            margin-bottom: 40px;
+            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+        }
+
+        .directories-header {
+            font-size: 1.8em;
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 10px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+        }
+
+        .dir-header-actions {
+            display: flex;
+            gap: 10px;
+        }
+
+        .dir-add-btn {
+            background: linear-gradient(90deg, #00b894 0%, #00a085 100%);
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 20px;
+            font-size: 0.9em;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .dir-add-btn:hover {
+            transform: scale(1.05);
+            box-shadow: 0 5px 15px rgba(0, 184, 148, 0.4);
+        }
+
+        .dir-scan-btn {
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 10px 20px;
+            border-radius: 20px;
+            font-size: 0.9em;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .dir-scan-btn:hover {
+            transform: scale(1.05);
+            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+        }
+
+        .directories-list {
+            margin-top: 20px;
+        }
+
+        .dir-item {
+            background: white;
+            border-radius: 15px;
+            padding: 20px;
+            margin-bottom: 15px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+            transition: all 0.3s ease;
+            border: 2px solid #e0e0e0;
+        }
+
+        .dir-item:hover {
+            transform: translateY(-3px);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+        }
+
+        .dir-item.active {
+            border-color: #00b894;
+            background: linear-gradient(to right, rgba(0, 184, 148, 0.05), white);
+        }
+
+        .dir-item.offline {
+            border-color: #d63031;
+            background: linear-gradient(to right, rgba(214, 48, 49, 0.05), white);
+        }
+
+        .dir-info {
+            flex: 1;
+        }
+
+        .dir-path {
+            font-family: 'Courier New', monospace;
+            font-weight: bold;
+            color: #333;
+            font-size: 1.1em;
+            margin-bottom: 5px;
+        }
+
+        .dir-meta {
+            display: flex;
+            gap: 15px;
+            font-size: 0.85em;
+            color: #666;
+            margin-bottom: 10px;
+        }
+
+        .dir-iframe-container {
+            width: 100%;
+            height: 250px;
+            border-radius: 8px;
+            overflow: hidden;
+            background: white;
+            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
+            margin-top: 10px;
+        }
+
+        .dir-iframe {
+            width: 100%;
+            height: 100%;
+            border: none;
+        }
+
+        .dir-status {
+            display: inline-flex;
+            align-items: center;
+            gap: 5px;
+            font-weight: bold;
+        }
+
+        .dir-status.active {
+            color: #00b894;
+        }
+
+        .dir-status.offline {
+            color: #d63031;
+        }
+
+        .dir-actions {
+            display: flex;
+            gap: 10px;
+        }
+
+        .dir-action-btn {
+            padding: 8px 16px;
+            border: none;
+            border-radius: 10px;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            font-size: 0.85em;
+        }
+
+        .dir-action-btn.start {
+            background: linear-gradient(90deg, #00b894 0%, #00a085 100%);
+            color: white;
+        }
+
+        .dir-action-btn.restart {
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+        }
+
+        .dir-action-btn.delete {
+            background: #ff6b6b;
+            color: white;
+        }
+
+        .dir-action-btn.stop {
+            background: #e74c3c;
+            color: white;
+        }
+
+        .dir-action-btn.assign {
+            background: #f39c12;
+            color: white;
+        }
+
+        .dir-action-btn.open {
+            background: linear-gradient(90deg, #00b894 0%, #00a085 100%);
+            color: white;
+        }
+
+        .dir-action-btn:hover {
+            transform: scale(1.05);
+        }
+
+        .dir-action-btn:disabled {
+            background: #ccc;
+            cursor: not-allowed;
+            transform: none;
+        }
+
+        .empty-directories {
+            text-align: center;
+            padding: 40px;
+            color: #999;
+        }
+
+        .status-dot {
+            display: inline-block;
+            width: 10px;
+            height: 10px;
+            border-radius: 50%;
+            animation: pulse 2s infinite;
+        }
+
+        .status-dot.active {
+            background: #00b894;
+        }
+
+        .status-dot.offline {
+            background: #d63031;
+            animation: none;
+        }
+
+        .email-settings-section {
+            background: rgba(255, 255, 255, 0.95);
+            border-radius: 20px;
+            padding: 30px;
+            margin-bottom: 40px;
+            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+        }
+
+        .email-header {
+            font-size: 1.8em;
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 10px;
+        }
+
+        .email-content {
+            display: flex;
+            gap: 20px;
+            align-items: center;
+            flex-wrap: wrap;
+        }
+
+        .email-toggle {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+        }
+
+        .toggle-switch {
+            position: relative;
+            width: 60px;
+            height: 30px;
+            background: #ccc;
+            border-radius: 30px;
+            cursor: pointer;
+            transition: background 0.3s;
+        }
+
+        .toggle-switch.active {
+            background: #00b894;
+        }
+
+        .toggle-switch::after {
+            content: '';
+            position: absolute;
+            width: 24px;
+            height: 24px;
+            background: white;
+            border-radius: 50%;
+            top: 3px;
+            left: 3px;
+            transition: left 0.3s;
+        }
+
+        .toggle-switch.active::after {
+            left: 33px;
+        }
+
+        .email-input-group {
+            flex: 1;
+            min-width: 300px;
+        }
+
+        .email-input-group label {
+            display: block;
+            font-weight: bold;
+            color: #333;
+            margin-bottom: 5px;
+            font-size: 0.9em;
+        }
+
+        .email-input-group input {
+            width: 100%;
+            padding: 10px 15px;
+            border: 2px solid #ddd;
+            border-radius: 10px;
+            font-size: 1em;
+            transition: border-color 0.3s;
+        }
+
+        .email-input-group input:focus {
+            outline: none;
+            border-color: #667eea;
+        }
+
+        .save-email-btn {
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 10px 25px;
+            border-radius: 10px;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .save-email-btn:hover {
+            transform: scale(1.05);
+        }
+
+        .email-status {
+            display: flex;
+            align-items: center;
+            gap: 10px;
+            padding: 10px 20px;
+            border-radius: 10px;
+            background: #e8f5e9;
+            color: #2e7d32;
+            font-weight: bold;
+            font-size: 0.9em;
+        }
+
+        .email-status.disabled {
+            background: #ffebee;
+            color: #c62828;
+        }
+
+        .login-overlay {
+            position: fixed;
+            top: 0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            z-index: 10000;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+        }
+
+        .login-overlay.hidden {
+            display: none;
+        }
+
+        .login-box {
+            background: white;
+            border-radius: 20px;
+            padding: 50px;
+            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
+            text-align: center;
+            max-width: 400px;
+            width: 90%;
+            animation: slideUp 0.5s ease-out;
+        }
+
+        .login-logo {
+            font-size: 4em;
+            margin-bottom: 20px;
+        }
+
+        .login-title {
+            font-size: 2em;
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 10px;
+        }
+
+        .login-subtitle {
+            color: #666;
+            margin-bottom: 30px;
+        }
+
+        .login-input {
+            width: 100%;
+            padding: 15px;
+            border: 2px solid #ddd;
+            border-radius: 10px;
+            font-size: 1em;
+            margin-bottom: 20px;
+            transition: border-color 0.3s;
+        }
+
+        .login-input:focus {
+            outline: none;
+            border-color: #667eea;
+        }
+
+        .login-btn {
+            width: 100%;
+            background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+            color: white;
+            border: none;
+            padding: 15px;
+            border-radius: 10px;
+            font-size: 1.1em;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+        }
+
+        .login-btn:hover {
+            transform: scale(1.05);
+            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
+        }
+
+        .login-btn:disabled {
+            background: #ccc;
+            cursor: not-allowed;
+            transform: none;
+        }
+
+        .login-error {
+            background: #ffebee;
+            color: #c62828;
+            padding: 10px;
+            border-radius: 10px;
+            margin-bottom: 20px;
+            display: none;
+        }
+
+        .login-error.show {
+            display: block;
+        }
+
+        .logout-btn {
+            position: fixed;
+            top: 20px;
+            right: 20px;
+            background: rgba(255, 255, 255, 0.2);
+            color: white;
+            border: 2px solid white;
+            padding: 10px 20px;
+            border-radius: 20px;
+            font-weight: bold;
+            cursor: pointer;
+            transition: all 0.3s ease;
+            z-index: 1000;
+        }
+
+        .logout-btn:hover {
+            background: white;
+            color: #667eea;
+        }
+
+        .live-domains-section {
+            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+            border-radius: 20px;
+            padding: 30px;
+            margin-bottom: 40px;
+            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
+            color: white;
+        }
+
+        .domains-container {
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
+            gap: 20px;
+            margin-top: 20px;
+        }
+
+        .domain-card {
+            background: rgba(255, 255, 255, 0.15);
+            backdrop-filter: blur(10px);
+            border-radius: 12px;
+            padding: 20px;
+            border: 2px solid rgba(255, 255, 255, 0.2);
+            transition: all 0.3s ease;
+            display: flex;
+            flex-direction: column;
+            gap: 12px;
+        }
+
+        .domain-card:hover {
+            transform: translateY(-5px);
+            background: rgba(255, 255, 255, 0.25);
+            border-color: rgba(255, 255, 255, 0.4);
+        }
+
+        .domain-card.live {
+            border-color: #00b894;
+            background: rgba(0, 184, 148, 0.2);
+        }
+
+        .domain-status {
+            width: 12px;
+            height: 12px;
+            border-radius: 50%;
+            background: #00b894;
+            box-shadow: 0 0 10px #00b894;
+            animation: pulse 2s infinite;
+        }
+
+        .domain-name {
+            flex: 1;
+            font-weight: 600;
+            font-size: 1.1em;
+        }
+
+        .domain-link {
+            color: white;
+            text-decoration: none;
+            opacity: 0.8;
+            transition: opacity 0.3s;
+        }
+
+        .domain-link:hover {
+            opacity: 1;
+        }
+
+        .domain-iframe-container {
+            width: 100%;
+            height: 300px;
+            border-radius: 8px;
+            overflow: hidden;
+            background: white;
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
+        }
+
+        .domain-iframe {
+            width: 100%;
+            height: 100%;
+            border: none;
+        }
+
+        @keyframes pulse {
+            0%, 100% { opacity: 1; }
+            50% { opacity: 0.5; }
+        }
+
+        .all-dirs-section {
+            background: rgba(255, 255, 255, 0.95);
+            border-radius: 20px;
+            padding: 30px;
+            margin-bottom: 40px;
+            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+        }
+
+        .all-dirs-header {
+            font-size: 1.8em;
+            font-weight: bold;
+            color: #667eea;
+            margin-bottom: 20px;
+            display: flex;
+            justify-content: space-between;
+            align-items: center;
+        }
+
+        .all-dirs-grid {
+            display: grid;
+            grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
+            gap: 15px;
+            margin-top: 20px;
+        }
+
+        .all-dir-card {
+            background: white;
+            border: 2px solid #e0e0e0;
+            border-radius: 12px;
+            padding: 15px;
+            transition: all 0.3s ease;
+            cursor: pointer;
+        }
+
+        .all-dir-card:hover {
+            transform: translateY(-3px);
+            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
+            border-color: #667eea;
+        }
+
+        .all-dir-card.tracked {
+            background: linear-gradient(to right, rgba(0, 184, 148, 0.05), white);
+            border-color: #00b894;
+        }
+
+        .all-dir-name {
+            font-family: 'Courier New', monospace;
+            font-weight: bold;
+            color: #333;
+            font-size: 0.95em;
+            margin-bottom: 10px;
+            word-break: break-all;
+        }
+
+        .all-dir-path {
+            font-family: 'Courier New', monospace;
+            font-size: 0.75em;
+            color: #999;
+            margin-bottom: 10px;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+        }
+
+        .all-dir-status {
+            display: inline-block;
+            padding: 4px 10px;
+            border-radius: 12px;
+            font-size: 0.75em;
+            font-weight: bold;
+            margin-bottom: 10px;
+        }
+
+        .all-dir-status.tracked {
+            background: #e8f5e9;
+            color: #2e7d32;
+        }
+
+        .all-dir-status.untracked {
+            background: #fff3cd;
+            color: #856404;
+        }
+
+        .all-dir-actions {
+            display: flex;
+            gap: 5px;
+        }
+
+        .all-dir-btn {
+            flex: 1;
+            padding: 8px 12px;
+            border: none;
+            border-radius: 8px;
+            font-weight: bold;
+            cursor: pointer;
+            font-size: 0.8em;
+            transition: all 0.3s ease;
+        }
+
+        .all-dir-btn:hover {
+            transform: scale(1.05);
+        }
+
+        .all-dir-btn.add {
+            background: linear-gradient(90deg, #00b894 0%, #00a085 100%);
+            color: white;
+        }
+
+        .all-dir-btn.tracked {
+            background: #e0e0e0;
+            color: #666;
+            cursor: default;
+        }
+
+        .loading-dirs {
+            text-align: center;
+            padding: 40px;
+            color: #999;
+        }
+    </style>
+</head>
+<body>
+    <!-- Login Overlay -->
+    <div class="login-overlay" id="loginOverlay">
+        <div class="login-box">
+            <div class="login-logo">🔒</div>
+            <div class="login-title">Port Viewer</div>
+            <div class="login-subtitle">Enter password to access</div>
+            <div class="login-error" id="loginError">Invalid password</div>
+            <form onsubmit="handleLogin(event)">
+                <input
+                    type="password"
+                    class="login-input"
+                    id="loginPassword"
+                    placeholder="Password"
+                    autocomplete="current-password"
+                    required
+                />
+                <button type="submit" class="login-btn" id="loginBtn">
+                    🔓 Login
+                </button>
+            </form>
+        </div>
+    </div>
+
+    <button class="logout-btn" id="logoutBtn" onclick="handleLogout()" style="display: none;">
+        🚪 Logout
+    </button>
+
+    <div class="container">
+        <header>
+            <h1>🚀 Port Viewer</h1>
+            <p class="subtitle">Active Network Ports & Services</p>
+            <div class="stats" id="stats"></div>
+        </header>
+
+        <!-- Live Domains Section -->
+        <div class="live-domains-section">
+            <div class="all-dirs-header">
+                <span>🌐 Live Domains</span>
+                <button class="dir-scan-btn" onclick="loadDomains()">🔄 Refresh</button>
+            </div>
+            <div id="domainsContainer">
+                <div class="loading-dirs">
+                    <div class="spinner"></div>
+                    <p>Loading domains...</p>
+                </div>
+            </div>
+        </div>
+
+        <!-- All Server Directories Section -->
+        <div class="all-dirs-section">
+            <div class="all-dirs-header">
+                <span>📂 All Server Directories</span>
+                <button class="dir-scan-btn" onclick="loadAllServerDirectories()">🔄 Refresh</button>
+            </div>
+            <div id="allDirsContainer">
+                <div class="loading-dirs">
+                    <div class="spinner"></div>
+                    <p>Scanning server directories...</p>
+                </div>
+            </div>
+        </div>
+
+        <!-- Email Alert Settings -->
+        <div class="email-settings-section">
+            <div class="email-header">📧 Email Alerts</div>
+            <div class="email-content">
+                <div class="email-toggle">
+                    <span style="font-weight: bold;">Alerts:</span>
+                    <div class="toggle-switch" id="emailToggle" onclick="toggleEmailAlerts()"></div>
+                </div>
+                <div class="email-input-group">
+                    <label>Email Address</label>
+                    <input type="email" id="emailRecipient" placeholder="your@email.com" />
+                </div>
+                <button class="save-email-btn" onclick="saveEmailConfig()">💾 Save Settings</button>
+                <div class="email-status" id="emailStatus">
+                    <span>⏱️ Monitoring every 60 seconds</span>
+                </div>
+            </div>
+        </div>
+
+        <!-- Server Directories Section -->
+        <div class="directories-section">
+            <div class="directories-header">
+                <span>📁 Server Directories</span>
+                <div class="dir-header-actions">
+                    <button class="dir-scan-btn" onclick="scanAllDirectories()">🔍 Scan All</button>
+                    <button class="dir-add-btn" onclick="openAddDirectoryModal()">➕ Add Directory</button>
+                </div>
+            </div>
+            <div class="directories-list" id="directoriesList">
+                <div class="empty-directories">
+                    Loading directories...
+                </div>
+            </div>
+        </div>
+
+        <!-- Ask the Server Section -->
+        <div class="ask-server-section">
+            <div class="ask-header">💬 Ask the Server</div>
+            <div class="ask-subtitle">
+                👋 Ask me anything about this server! Try: "What's the uptime?", "Show memory usage", "How many ports are open?", or type "help" for more commands.
+            </div>
+            <div class="ask-input-container">
+                <input
+                    type="text"
+                    class="ask-input"
+                    id="askInput"
+                    placeholder="Ask about uptime, memory, CPU, disk, ports, processes..."
+                    onkeypress="if(event.key === 'Enter') askServer()"
+                />
+                <button class="ask-btn" id="askBtn" onclick="askServer()">Send 🚀</button>
+            </div>
+            <div class="quick-queries">
+                <button class="quick-query-btn" onclick="quickQuery('uptime')">⏱️ Uptime</button>
+                <button class="quick-query-btn" onclick="quickQuery('memory')">💾 Memory</button>
+                <button class="quick-query-btn" onclick="quickQuery('cpu')">🖥️ CPU</button>
+                <button class="quick-query-btn" onclick="quickQuery('disk')">💿 Disk</button>
+                <button class="quick-query-btn" onclick="quickQuery('ports')">🔌 Ports</button>
+                <button class="quick-query-btn" onclick="quickQuery('processes')">⚙️ Processes</button>
+                <button class="quick-query-btn" onclick="quickQuery('help')">❓ Help</button>
+            </div>
+            <div class="ask-response" id="askResponse"></div>
+        </div>
+
+        <div id="loading" class="loading">
+            <div class="spinner"></div>
+            <p>Loading ports...</p>
+        </div>
+
+        <div id="error" style="display: none;"></div>
+        <div class="cards-grid" id="cards"></div>
+    </div>
+
+    <button class="refresh-btn" onclick="loadPorts()">🔄 Refresh</button>
+
+    <!-- Add Directory Modal -->
+    <div class="modal" id="addDirModal">
+        <div class="modal-content">
+            <div class="modal-header">📁 Add Directory</div>
+            <form id="addDirForm" onsubmit="handleAddDirectory(event)">
+                <div class="form-group">
+                    <label class="form-label">Directory Path *</label>
+                    <input type="text" class="form-input" id="dirPath" placeholder="/root/my-app" required>
+                </div>
+                <div class="form-group">
+                    <label class="form-label">Port Number (optional)</label>
+                    <input type="number" class="form-input" id="dirPort" placeholder="3000">
+                </div>
+                <div class="form-group">
+                    <label class="form-label">App Name (optional)</label>
+                    <input type="text" class="form-input" id="dirAppName" placeholder="My App">
+                </div>
+                <div class="form-buttons">
+                    <button type="button" class="btn-cancel" onclick="closeAddDirectoryModal()">Cancel</button>
+                    <button type="submit" class="btn-submit">Add Directory</button>
+                </div>
+            </form>
+        </div>
+    </div>
+
+    <!-- Assign Port Modal -->
+    <div class="modal" id="assignPortModal">
+        <div class="modal-content">
+            <div class="modal-header">🔢 Assign Port</div>
+            <form id="assignPortForm" onsubmit="handleAssignPort(event)">
+                <div class="form-group">
+                    <label class="form-label">Directory</label>
+                    <input type="text" class="form-input" id="assignDirPath" readonly>
+                </div>
+                <div class="form-group">
+                    <label class="form-label">Port Number *</label>
+                    <input type="number" class="form-input" id="assignPort" placeholder="3000" required>
+                </div>
+                <div class="form-group">
+                    <label class="form-label">App Name (optional)</label>
+                    <input type="text" class="form-input" id="assignAppName" placeholder="My App">
+                </div>
+                <div class="form-buttons">
+                    <button type="button" class="btn-cancel" onclick="closeAssignPortModal()">Cancel</button>
+                    <button type="submit" class="btn-submit">Assign Port</button>
+                </div>
+            </form>
+        </div>
+    </div>
+
+    <!-- Scan Directories Modal -->
+    <div class="modal" id="scanDirModal">
+        <div class="modal-content" style="max-width: 700px;">
+            <div class="modal-header">🔍 All Server Directories</div>
+            <div id="scanDirList" style="max-height: 400px; overflow-y: auto; margin: 20px 0;">
+                <div style="text-align: center; padding: 20px;">
+                    <div class="spinner"></div>
+                    <p>Scanning directories...</p>
+                </div>
+            </div>
+            <div class="form-buttons">
+                <button type="button" class="btn-cancel" onclick="closeScanDirModal()">Close</button>
+            </div>
+        </div>
+    </div>
+
+    <div class="preview-overlay" id="previewOverlay" onclick="closePreview()"></div>
+    <div class="preview-container" id="previewContainer">
+        <div class="preview-header">
+            <div class="preview-title" id="previewTitle"></div>
+            <button class="close-preview" onclick="closePreview()">×</button>
+        </div>
+        <iframe class="preview-iframe" id="previewIframe"></iframe>
+    </div>
+
+    <script>
+        // Authentication Management
+        let authToken = localStorage.getItem('authToken');
+        let serverInfo = { serverIp: 'localhost', port: 6000 }; // Default values
+
+        // Fetch server info on page load
+        fetch('/api/server-info')
+            .then(response => response.json())
+            .then(info => {
+                serverInfo = info;
+            })
+            .catch(err => console.error('Failed to fetch server info:', err));
+
+        // Check if authenticated on page load
+        if (!authToken) {
+            document.getElementById('loginOverlay').classList.remove('hidden');
+        } else {
+            document.getElementById('loginOverlay').classList.add('hidden');
+            document.getElementById('logoutBtn').style.display = 'block';
+            initializeApp();
+        }
+
+        async function handleLogin(event) {
+            event.preventDefault();
+
+            const password = document.getElementById('loginPassword').value;
+            const btn = document.getElementById('loginBtn');
+            const errorDiv = document.getElementById('loginError');
+
+            btn.disabled = true;
+            btn.textContent = '🔄 Logging in...';
+            errorDiv.classList.remove('show');
+
+            try {
+                const response = await fetch('/api/login', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify({ password })
+                });
+
+                const result = await response.json();
+
+                if (result.success && result.token) {
+                    authToken = result.token;
+                    localStorage.setItem('authToken', authToken);
+
+                    document.getElementById('loginOverlay').classList.add('hidden');
+                    document.getElementById('logoutBtn').style.display = 'block';
+                    document.getElementById('loginPassword').value = '';
+
+                    initializeApp();
+                } else {
+                    errorDiv.classList.add('show');
+                    btn.disabled = false;
+                    btn.textContent = '🔓 Login';
+                }
+            } catch (err) {
+                errorDiv.textContent = 'Connection error. Please try again.';
+                errorDiv.classList.add('show');
+                btn.disabled = false;
+                btn.textContent = '🔓 Login';
+            }
+        }
+
+        async function handleLogout() {
+            if (!confirm('Are you sure you want to logout?')) return;
+
+            try {
+                await fetch('/api/logout', {
+                    method: 'POST',
+                    headers: {
+                        'Authorization': `Bearer ${authToken}`
+                    }
+                });
+            } catch (err) {
+                console.error('Logout error:', err);
+            }
+
+            authToken = null;
+            localStorage.removeItem('authToken');
+            document.getElementById('loginOverlay').classList.remove('hidden');
+            document.getElementById('logoutBtn').style.display = 'none';
+        }
+
+        // Wrapper for authenticated fetch
+        async function authFetch(url, options = {}) {
+            if (!options.headers) options.headers = {};
+            options.headers['Authorization'] = `Bearer ${authToken}`;
+
+            const response = await fetch(url, options);
+
+            if (response.status === 401) {
+                // Session expired
+                authToken = null;
+                localStorage.removeItem('authToken');
+                document.getElementById('loginOverlay').classList.remove('hidden');
+                document.getElementById('logoutBtn').style.display = 'none';
+                throw new Error('Session expired. Please login again.');
+            }
+
+            return response;
+        }
+
+        function initializeApp() {
+            loadPorts();
+            loadDirectories();
+            loadEmailConfig();
+            loadDomains();
+            loadAllServerDirectories();
+
+            // Auto-refresh every 2 minutes
+            setInterval(() => {
+                loadPorts();
+                loadDirectories();
+                loadDomains();
+            }, 120000);
+        }
+
+        // Load live domains
+        async function loadDomains() {
+            const container = document.getElementById('domainsContainer');
+            container.innerHTML = `
+                <div class="loading-dirs">
+                    <div class="spinner"></div>
+                    <p>Loading domains...</p>
+                </div>
+            `;
+
+            try {
+                const response = await authFetch('/api/domains');
+                const data = await response.json();
+
+                if (data.domains && data.domains.length > 0) {
+                    const grid = document.createElement('div');
+                    grid.className = 'domains-container';
+
+                    data.domains.forEach(domain => {
+                        const card = document.createElement('div');
+                        card.className = 'domain-card live';
+
+                        const proxyUrl = `/api/proxy/${encodeURIComponent('https://' + domain)}`;
+
+                        card.innerHTML = `
+                            <div style="display: flex; align-items: center; gap: 12px;">
+                                <div class="domain-status"></div>
+                                <div class="domain-name">${domain}</div>
+                                <a href="https://${domain}" target="_blank" class="domain-link">🔗</a>
+                            </div>
+                            <div class="domain-iframe-container">
+                                <iframe class="domain-iframe" src="${proxyUrl}" sandbox="allow-same-origin allow-scripts allow-forms allow-popups"></iframe>
+                            </div>
+                        `;
+
+                        grid.appendChild(card);
+                    });
+
+                    container.innerHTML = '';
+                    container.appendChild(grid);
+                } else {
+                    container.innerHTML = `
+                        <div class="loading-dirs">
+                            🌐 No domains configured
+                        </div>
+                    `;
+                }
+            } catch (err) {
+                container.innerHTML = `
+                    <div class="loading-dirs" style="color: #fff3cd;">
+                        ❌ Error: ${err.message}
+                    </div>
+                `;
+            }
+        }
+
+        // Load all server directories
+        async function loadAllServerDirectories() {
+            const container = document.getElementById('allDirsContainer');
+            container.innerHTML = `
+                <div class="loading-dirs">
+                    <div class="spinner"></div>
+                    <p>Scanning server directories...</p>
+                </div>
+            `;
+
+            try {
+                // Fetch all directories
+                const scanResponse = await authFetch('/api/scan-directories');
+                const scanResult = await scanResponse.json();
+
+                // Fetch tracked ports to know which are already tracked
+                const portsResponse = await authFetch('/api/ports');
+                const ports = await portsResponse.json();
+
+                // Fetch directory data to get lastSeen timestamps
+                const dirDataResponse = await authFetch('/api/directories');
+                const directoryData = await dirDataResponse.json();
+
+                const trackedDirs = new Set();
+                ports.forEach(port => {
+                    if (port.directory && port.isUserApp) {
+                        trackedDirs.add(port.directory);
+                    }
+                });
+
+                if (scanResult.directories && scanResult.directories.length > 0) {
+                    // Sort directories by most recently accessed
+                    const sortedDirs = scanResult.directories.sort((a, b) => {
+                        const aData = directoryData[a];
+                        const bData = directoryData[b];
+
+                        // If both have lastSeen, sort by timestamp (most recent first)
+                        if (aData?.lastSeen && bData?.lastSeen) {
+                            return new Date(bData.lastSeen) - new Date(aData.lastSeen);
+                        }
+                        // Tracked directories come before untracked
+                        if (aData?.lastSeen) return -1;
+                        if (bData?.lastSeen) return 1;
+                        // Both untracked - maintain alphabetical order
+                        return a.localeCompare(b);
+                    });
+
+                    const grid = document.createElement('div');
+                    grid.className = 'all-dirs-grid';
+
+                    sortedDirs.forEach(dir => {
+                        const isTracked = trackedDirs.has(dir);
+                        const dirName = dir.split('/').pop();
+                        const dirData = directoryData[dir];
+                        const lastPort = dirData?.lastPort || 'N/A';
+
+                        const card = document.createElement('div');
+                        card.className = `all-dir-card ${isTracked ? 'tracked' : ''}`;
+
+                        card.innerHTML = `
+                            <div class="all-dir-name">📁 ${dirName}</div>
+                            <div class="all-dir-path" title="${dir}">${dir}</div>
+                            <div class="all-dir-status ${isTracked ? 'tracked' : 'untracked'}">
+                                ${isTracked ? '✅ Tracked' : '⚠️ Not Tracked'}
+                            </div>
+                            ${dirData?.lastPort ? `<div style="font-size: 0.75em; color: #666; margin: 5px 0;">Last Port: ${lastPort}</div>` : ''}
+                            <div class="all-dir-actions">
+                                ${isTracked
+                                    ? '<button class="all-dir-btn tracked" disabled>Already Tracked</button>'
+                                    : `<button class="all-dir-btn add" onclick="quickAddDirectory(event, '${dir}', '${dirName}')">➕ Add & Start</button>`
+                                }
+                            </div>
+                        `;
+
+                        grid.appendChild(card);
+                    });
+
+                    container.innerHTML = '';
+                    container.appendChild(grid);
+                } else {
+                    container.innerHTML = `
+                        <div class="loading-dirs">
+                            📂 No Node.js projects found on the server
+                        </div>
+                    `;
+                }
+            } catch (err) {
+                container.innerHTML = `
+                    <div class="loading-dirs" style="color: #d63031;">
+                        ❌ Error: ${err.message}
+                    </div>
+                `;
+            }
+        }
+
+        async function quickAddDirectory(event, directory, appName) {
+            // Fetch directory data to check if there's a last port
+            const dirDataResponse = await authFetch('/api/directories');
+            const directoryData = await dirDataResponse.json();
+            const lastPort = directoryData[directory]?.lastPort;
+
+            // Suggest an available port if no last port
+            let suggestedPort = lastPort || '3000';
+            if (!lastPort) {
+                try {
+                    const portResponse = await authFetch('/api/suggest-port');
+                    const portData = await portResponse.json();
+                    suggestedPort = portData.port;
+                } catch (err) {
+                    console.error('Failed to get suggested port:', err);
+                }
+            }
+
+            const button = event.target;
+            const card = button.closest('.all-dir-card');
+            const originalButtonHTML = button.innerHTML;
+
+            const port = prompt(`Enter port number for ${appName} (suggested: ${suggestedPort}):`, suggestedPort);
+            if (!port) return;
+
+            // Start loading spinner IMMEDIATELY after OK is clicked
+            button.disabled = true;
+            button.innerHTML = '⏳ Adding<span class="loading-spinner"></span>';
+
+            card.classList.add('loading');
+            const overlay = document.createElement('div');
+            overlay.className = 'loading-overlay';
+            overlay.innerHTML = `
+                <div class="big-spinner"></div>
+                <div class="loading-text">🚀 Adding & starting ${appName}...</div>
+            `;
+            card.appendChild(overlay);
+
+            try {
+                // Add the directory
+                overlay.querySelector('.loading-text').textContent = `📝 Adding ${appName}...`;
+                const addResponse = await authFetch('/api/add-directory', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify({
+                        directory: directory,
+                        port: port,
+                        appName: appName
+                    })
+                });
+
+                const addResult = await addResponse.json();
+
+                if (addResult.success) {
+                    // Now start it
+                    overlay.querySelector('.loading-text').textContent = `🚀 Starting ${appName}...`;
+                    button.innerHTML = '⏳ Starting<span class="loading-spinner"></span>';
+
+                    const startResponse = await authFetch(`/api/restart/${encodeURIComponent(directory)}`);
+                    const startResult = await startResponse.json();
+
+                    if (startResult.success) {
+                        overlay.querySelector('.loading-text').textContent = `✅ ${appName} started successfully!`;
+                        button.innerHTML = '✅ Started!';
+                        button.style.background = '#00b894';
+
+                        // ALWAYS open the app
+                        setTimeout(() => {
+                            const url = `http://${serverInfo.serverIp}:${port}`;
+                            window.open(url, '_blank');
+                        }, 1500);
+
+                        // Reload everything
+                        setTimeout(() => {
+                            card.classList.remove('loading');
+                            overlay.remove();
+                            loadAllServerDirectories();
+                            loadDirectories();
+                            loadPorts();
+                        }, 3000);
+                    } else {
+                        overlay.querySelector('.loading-text').textContent = `❌ Failed to start`;
+                        button.innerHTML = '❌ Failed';
+                        button.style.background = '#d63031';
+                        setTimeout(() => {
+                            card.classList.remove('loading');
+                            overlay.remove();
+                            button.innerHTML = originalButtonHTML;
+                            button.style.background = '';
+                            button.disabled = false;
+                        }, 3000);
+                        alert(`✅ ${appName} added but failed to start: ${startResult.message || startResult.error}`);
+                    }
+                } else {
+                    card.classList.remove('loading');
+                    overlay.remove();
+                    button.innerHTML = originalButtonHTML;
+                    button.disabled = false;
+                    alert(`❌ Error: ${addResult.error}`);
+                }
+            } catch (err) {
+                card.classList.remove('loading');
+                if (overlay && overlay.parentNode) overlay.remove();
+                button.innerHTML = originalButtonHTML;
+                button.disabled = false;
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        async function loadPorts() {
+            const loading = document.getElementById('loading');
+            const cards = document.getElementById('cards');
+            const error = document.getElementById('error');
+            const stats = document.getElementById('stats');
+
+            loading.style.display = 'block';
+            cards.innerHTML = '';
+            error.style.display = 'none';
+
+            try {
+                const response = await authFetch('/api/ports');
+                const ports = await response.json();
+
+                loading.style.display = 'none';
+
+                if (ports.error) {
+                    error.innerHTML = `<div class="error">${ports.error}</div>`;
+                    error.style.display = 'block';
+                    return;
+                }
+
+                // Display stats
+                const tcpCount = ports.filter(p => p.protocol.includes('TCP')).length;
+                const udpCount = ports.filter(p => p.protocol.includes('UDP')).length;
+
+                stats.innerHTML = `
+                    <div class="stat-box">
+                        <span class="stat-number">${ports.length}</span>
+                        <div>Total Ports</div>
+                    </div>
+                    <div class="stat-box">
+                        <span class="stat-number">${tcpCount}</span>
+                        <div>TCP</div>
+                    </div>
+                    <div class="stat-box">
+                        <span class="stat-number">${udpCount}</span>
+                        <div>UDP</div>
+                    </div>
+                `;
+
+                // Display cards
+                ports.forEach((port, index) => {
+                    const card = document.createElement('div');
+                    card.className = 'card';
+                    card.style.animationDelay = `${index * 0.05}s`;
+
+                    // Add special classes for user apps
+                    if (port.isUserApp && port.directory) {
+                        if (port.isOffline) {
+                            card.className += ' offline-app';
+                        } else {
+                            card.className += ' active-user-app';
+                        }
+                    }
+
+                    const protocolClass = `protocol-${port.protocol.toLowerCase()}`;
+
+                    let websiteLinksHTML = '';
+                    let webViewHTML = '';
+                    if (port.websites && port.websites.length > 0) {
+                        websiteLinksHTML = '<div class="website-links">';
+                        const firstWebsite = port.websites[0];
+                        const firstUrl = firstWebsite.includes('localhost') || firstWebsite.includes('127.0.0.1')
+                            ? `http://localhost:${port.port}`
+                            : `https://${firstWebsite}`;
+
+                        port.websites.forEach(website => {
+                            const url = website.includes('localhost') || website.includes('127.0.0.1')
+                                ? `http://localhost:${port.port}`
+                                : `https://${website}`;
+                            websiteLinksHTML += `
+                                <a href="${url}" class="website-link" target="_blank"
+                                   onmouseover="showPreview('${url}', '${website}')"
+                                   onmouseout="hidePreviewDelayed()">
+                                    ${website} <span class="open-icon">↗</span>
+                                </a>
+                            `;
+                        });
+                        websiteLinksHTML += '</div>';
+
+                        // Add web view container for the first website - always visible
+                        // Use direct URL for better CSS loading, fallback to proxy if needed
+                        const directUrl = firstUrl;
+                        const proxyUrl = `/api/proxy/${encodeURIComponent(firstUrl)}`;
+                        webViewHTML = `
+                            <div class="web-view-container">
+                                <div class="web-view-iframe-container active" id="webview-${port.port}">
+                                    <button class="fullscreen-btn" onclick="openFullscreen('${firstUrl}', '${firstWebsite}')">⛶ Fullscreen</button>
+                                    <iframe class="web-view-iframe" id="iframe-${port.port}" src="${directUrl}" sandbox="allow-same-origin allow-scripts allow-forms allow-popups"></iframe>
+                                </div>
+                            </div>
+                        `;
+                    }
+
+                    let dockerHTML = '';
+                    if (port.docker) {
+                        dockerHTML = `<div class="docker-badge">🐳 ${port.docker.image}</div>`;
+                    }
+
+                    let directoryHTML = '';
+                    let restartButtonHTML = '';
+                    let appTypeHTML = '';
+                    let statusHTML = '';
+
+                    if (port.isUserApp && port.directory) {
+                        if (port.isOffline) {
+                            appTypeHTML = '<div class="offline-badge">❌ OFFLINE</div>';
+                            statusHTML = `<div class="last-port-info">Last Port: ${port.port}</div>`;
+                            restartButtonHTML = `<button class="restart-btn" onclick="restartApp('${port.directory}', '${port.port}')">🔄 Start App</button>`;
+                        } else {
+                            appTypeHTML = '<span class="user-app-indicator">👤 User App <span class="active-indicator"></span></span>';
+                            restartButtonHTML = `<button class="restart-btn" onclick="restartApp('${port.directory}', '${port.port}')">🔄 Restart App</button>`;
+                        }
+                        directoryHTML = `<div class="directory-info">📁 ${port.directory}</div>`;
+                    } else if (!port.isUserApp && port.port) {
+                        const sysPort = parseInt(port.port);
+                        if ([22, 53, 80, 443, 111, 123, 514].includes(sysPort)) {
+                            appTypeHTML = '<span class="system-badge">⚙️ System Service</span>';
+                        }
+                    }
+
+                    let protocolBadge = port.protocol !== 'N/A' ? `<span class="protocol-badge ${protocolClass}">${port.protocol}</span>` : '';
+
+                    card.innerHTML = `
+                        <div class="port-number">:${port.port}</div>
+                        ${port.appName ? `<div class="app-name">📱 ${port.appName}</div>` : ''}
+                        ${appTypeHTML}
+                        ${statusHTML}
+                        ${websiteLinksHTML}
+                        ${webViewHTML}
+                        ${dockerHTML}
+                        ${directoryHTML}
+                        <div class="port-info">
+                            <div class="info-label">Address</div>
+                            <div class="info-value">${port.address}</div>
+                        </div>
+                        <div class="port-info">
+                            <div class="info-label">Process</div>
+                            <div class="info-value">${port.process}</div>
+                        </div>
+                        ${protocolBadge}
+                        ${restartButtonHTML}
+                    `;
+
+                    cards.appendChild(card);
+                });
+
+                if (ports.length === 0) {
+                    cards.innerHTML = '<div class="error">No listening ports found</div>';
+                }
+            } catch (err) {
+                loading.style.display = 'none';
+                error.innerHTML = `<div class="error">Failed to load ports: ${err.message}</div>`;
+                error.style.display = 'block';
+            }
+        }
+
+
+        // Email configuration functions
+        async function loadEmailConfig() {
+            try {
+                const response = await authFetch('/api/email-config');
+                const config = await response.json();
+
+                document.getElementById('emailRecipient').value = config.recipient || '';
+                const toggle = document.getElementById('emailToggle');
+                const status = document.getElementById('emailStatus');
+
+                if (config.enabled) {
+                    toggle.classList.add('active');
+                    status.classList.remove('disabled');
+                    status.innerHTML = '<span>✅ Alerts Enabled - Monitoring every 60 seconds</span>';
+                } else {
+                    toggle.classList.remove('active');
+                    status.classList.add('disabled');
+                    status.innerHTML = '<span>❌ Alerts Disabled</span>';
+                }
+            } catch (err) {
+                console.error('Failed to load email config:', err);
+            }
+        }
+
+        function toggleEmailAlerts() {
+            const toggle = document.getElementById('emailToggle');
+            toggle.classList.toggle('active');
+        }
+
+        async function saveEmailConfig() {
+            const enabled = document.getElementById('emailToggle').classList.contains('active');
+            const recipient = document.getElementById('emailRecipient').value.trim();
+
+            if (!recipient) {
+                alert('Please enter an email address');
+                return;
+            }
+
+            try {
+                const response = await authFetch('/api/email-config', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify({ enabled, recipient })
+                });
+
+                const result = await response.json();
+
+                if (result.success) {
+                    alert('✅ Email settings saved successfully!');
+                    loadEmailConfig();
+                } else {
+                    alert(`❌ Error: ${result.error}`);
+                }
+            } catch (err) {
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        // Load and display directories
+        async function loadDirectories() {
+            try {
+                const response = await authFetch('/api/ports');
+                const ports = await response.json();
+
+                // Extract unique directories and their status
+                const directories = {};
+
+                ports.forEach(port => {
+                    if (port.directory && port.isUserApp) {
+                        directories[port.directory] = {
+                            path: port.directory,
+                            appName: port.appName || path.basename(port.directory),
+                            port: port.port,
+                            isActive: !port.isOffline,
+                            process: port.process,
+                            lastSeen: new Date().toISOString()
+                        };
+                    }
+                });
+
+                displayDirectories(directories);
+            } catch (err) {
+                console.error('Failed to load directories:', err);
+            }
+        }
+
+        function displayDirectories(directories) {
+            const container = document.getElementById('directoriesList');
+            const dirArray = Object.values(directories);
+
+            if (dirArray.length === 0) {
+                container.innerHTML = `
+                    <div class="empty-directories">
+                        📂 No directories tracked yet. Click "Add Directory" to add one!
+                    </div>
+                `;
+                return;
+            }
+
+            container.innerHTML = dirArray.map(dir => {
+                const statusClass = dir.isActive ? 'active' : 'offline';
+                const statusText = dir.isActive ? 'ONLINE' : 'OFFLINE';
+
+                let actionButtons = '';
+                if (dir.isActive) {
+                    actionButtons = `
+                        <button class="dir-action-btn open" onclick="openApp('${dir.port}')">🌐 Open</button>
+                        <button class="dir-action-btn restart" onclick="restartDirectory('${dir.path}', '${dir.port}')">🔄 Restart</button>
+                        <button class="dir-action-btn stop" onclick="stopPort('${dir.port}')">⏹️ Stop</button>
+                    `;
+                } else {
+                    actionButtons = `
+                        <button class="dir-action-btn start" onclick="restartDirectory('${dir.path}', '${dir.port}')">▶️ Start</button>
+                    `;
+                }
+
+                const iframeHTML = dir.isActive ? `
+                    <div class="dir-iframe-container">
+                        <iframe class="dir-iframe" src="/api/proxy/${encodeURIComponent('http://' + serverInfo.serverIp + ':' + dir.port)}" sandbox="allow-same-origin allow-scripts allow-forms allow-popups"></iframe>
+                    </div>
+                ` : '';
+
+                return `
+                    <div class="dir-item ${statusClass}">
+                        <div class="dir-info">
+                            <div class="dir-path">📁 ${dir.path}</div>
+                            <div class="dir-meta">
+                                <span class="dir-status ${statusClass}">
+                                    <span class="status-dot ${statusClass}"></span>
+                                    ${statusText}
+                                </span>
+                                <span>Port: ${dir.port}</span>
+                                <span>App: ${dir.appName}</span>
+                            </div>
+                            ${iframeHTML}
+                        </div>
+                        <div class="dir-actions">
+                            ${actionButtons}
+                            <button class="dir-action-btn assign" onclick="openAssignPortModal('${dir.path}', '${dir.port}', '${dir.appName}')">🔢 Assign</button>
+                            <button class="dir-action-btn delete" onclick="deleteDirectory('${dir.path}')">🗑️ Remove</button>
+                        </div>
+                    </div>
+                `;
+            }).join('');
+        }
+
+        async function deleteDirectory(directory) {
+            if (!confirm(`Are you sure you want to remove ${directory} from tracking?\n\nThis won't delete the actual directory, just stop tracking it.`)) {
+                return;
+            }
+
+            try {
+                const response = await authFetch(`/api/delete-directory/${encodeURIComponent(directory)}`);
+                const result = await response.json();
+
+                if (result.success) {
+                    loadDirectories();
+                    loadPorts();
+                } else {
+                    alert(`❌ Error: ${result.error}`);
+                }
+            } catch (err) {
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        async function restartDirectory(directory, port) {
+            if (!confirm(`Are you sure you want to restart the app in ${directory}?`)) {
+                return;
+            }
+
+            const button = event.target;
+            const originalText = button.textContent;
+            button.disabled = true;
+            button.innerHTML = 'Starting<span class="loading-spinner"></span>';
+
+            try {
+                const response = await authFetch(`/api/restart/${encodeURIComponent(directory)}`);
+                const result = await response.json();
+
+                if (result.success) {
+                    button.innerHTML = '✅ Started!';
+                    button.style.background = '#00b894';
+
+                    // Always open the URL in a new tab
+                    const portToOpen = result.lastPort || port;
+                    if (portToOpen) {
+                        setTimeout(() => {
+                            const url = `http://${serverInfo.serverIp}:${portToOpen}`;
+                            window.open(url, '_blank');
+                        }, 1500);
+                    }
+
+                    // Reload directories after a delay
+                    setTimeout(() => {
+                        loadDirectories();
+                        loadPorts();
+                        button.textContent = originalText;
+                        button.style.background = '';
+                        button.disabled = false;
+                    }, 3000);
+                } else {
+                    button.textContent = '❌ Failed';
+                    button.style.background = '#d63031';
+                    setTimeout(() => {
+                        button.textContent = originalText;
+                        button.style.background = '';
+                        button.disabled = false;
+                    }, 3000);
+                    alert(`❌ Failed to restart: ${result.error || result.message}`);
+                }
+            } catch (err) {
+                button.textContent = '❌ Error';
+                button.disabled = false;
+                setTimeout(() => {
+                    button.textContent = originalText;
+                }, 3000);
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        function openApp(port) {
+            const url = `http://${serverInfo.serverIp}:${port}`;
+            window.open(url, '_blank');
+        }
+
+        async function stopPort(port) {
+            if (!confirm(`Are you sure you want to stop the process on port ${port}?`)) {
+                return;
+            }
+
+            try {
+                const response = await authFetch(`/api/stop-port/${port}`, {
+                    method: 'POST'
+                });
+                const result = await response.json();
+
+                if (result.success) {
+                    alert(`✅ Stopped process on port ${port}`);
+                    loadDirectories();
+                    loadPorts();
+                } else {
+                    alert(`❌ Error: ${result.error || 'Failed to stop process'}`);
+                }
+            } catch (err) {
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        function openAssignPortModal(directory, currentPort, appName) {
+            document.getElementById('assignDirPath').value = directory;
+            document.getElementById('assignPort').value = currentPort || '';
+            document.getElementById('assignAppName').value = appName || '';
+            document.getElementById('assignPortModal').classList.add('active');
+        }
+
+        function closeAssignPortModal() {
+            document.getElementById('assignPortModal').classList.remove('active');
+            document.getElementById('assignPortForm').reset();
+        }
+
+        async function handleAssignPort(event) {
+            event.preventDefault();
+
+            const directory = document.getElementById('assignDirPath').value;
+            const port = document.getElementById('assignPort').value;
+            const appName = document.getElementById('assignAppName').value.trim();
+
+            try {
+                const response = await authFetch('/api/assign-port', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify({
+                        directory: directory,
+                        port: port,
+                        appName: appName || undefined
+                    })
+                });
+
+                const result = await response.json();
+
+                if (result.success) {
+                    closeAssignPortModal();
+                    alert(`✅ Port ${port} assigned to ${directory}`);
+                    loadDirectories();
+                    loadPorts();
+                } else {
+                    alert(`❌ Error: ${result.error}`);
+                }
+            } catch (err) {
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        async function scanAllDirectories() {
+            document.getElementById('scanDirModal').classList.add('active');
+            document.getElementById('scanDirList').innerHTML = `
+                <div style="text-align: center; padding: 20px;">
+                    <div class="spinner"></div>
+                    <p>Scanning directories...</p>
+                </div>
+            `;
+
+            try {
+                const response = await authFetch('/api/scan-directories');
+                const result = await response.json();
+
+                if (result.directories && result.directories.length > 0) {
+                    const listHTML = result.directories.map(dir => `
+                        <div class="dir-item" style="margin-bottom: 10px;">
+                            <div class="dir-info" style="flex: 1;">
+                                <div class="dir-path" style="font-size: 0.95em;">📁 ${dir}</div>
+                            </div>
+                            <div class="dir-actions">
+                                <button class="dir-action-btn" style="padding: 6px 12px; font-size: 0.8em;" onclick="addScannedDirectory('${dir}')">➕ Add</button>
+                            </div>
+                        </div>
+                    `).join('');
+
+                    document.getElementById('scanDirList').innerHTML = listHTML;
+                } else {
+                    document.getElementById('scanDirList').innerHTML = `
+                        <div class="empty-directories">
+                            No Node.js projects found
+                        </div>
+                    `;
+                }
+            } catch (err) {
+                document.getElementById('scanDirList').innerHTML = `
+                    <div class="error">Error: ${err.message}</div>
+                `;
+            }
+        }
+
+        function closeScanDirModal() {
+            document.getElementById('scanDirModal').classList.remove('active');
+        }
+
+        async function addScannedDirectory(directory) {
+            // Suggest an available port
+            let suggestedPort = '3000';
+            try {
+                const portResponse = await authFetch('/api/suggest-port');
+                const portData = await portResponse.json();
+                suggestedPort = portData.port;
+            } catch (err) {
+                console.error('Failed to get suggested port:', err);
+            }
+
+            const port = prompt(`Enter port number for ${directory} (suggested: ${suggestedPort}):`, suggestedPort);
+            if (!port) return;
+
+            try {
+                const response = await authFetch('/api/add-directory', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify({
+                        directory: directory,
+                        port: port,
+                        appName: directory.split('/').pop()
+                    })
+                });
+
+                const result = await response.json();
+
+                if (result.success) {
+                    alert(`✅ Directory added: ${directory}`);
+                    closeScanDirModal();
+                    loadDirectories();
+                    loadPorts();
+                } else {
+                    alert(`❌ Error: ${result.error}`);
+                }
+            } catch (err) {
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        // Close modals when clicking outside
+        document.getElementById('assignPortModal').addEventListener('click', (e) => {
+            if (e.target.id === 'assignPortModal') {
+                closeAssignPortModal();
+            }
+        });
+
+        document.getElementById('scanDirModal').addEventListener('click', (e) => {
+            if (e.target.id === 'scanDirModal') {
+                closeScanDirModal();
+            }
+        });
+
+        // Ask the Server functionality
+        async function askServer() {
+            const input = document.getElementById('askInput');
+            const btn = document.getElementById('askBtn');
+            const responseDiv = document.getElementById('askResponse');
+            const query = input.value.trim();
+
+            if (!query) return;
+
+            btn.disabled = true;
+            btn.textContent = 'Thinking...';
+            responseDiv.innerHTML = '';
+            responseDiv.classList.remove('show');
+
+            try {
+                const response = await authFetch('/api/ask', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify({ query })
+                });
+
+                const result = await response.json();
+
+                if (result.success) {
+                    responseDiv.innerHTML = `
+                        <div class="response-header">📊 ${result.query || query}</div>
+                        <div class="response-content">${result.response}</div>
+                    `;
+                } else {
+                    responseDiv.innerHTML = `
+                        <div class="response-error">❌ ${result.error}</div>
+                    `;
+                }
+
+                responseDiv.classList.add('show');
+            } catch (err) {
+                responseDiv.innerHTML = `
+                    <div class="response-error">❌ Error: ${err.message}</div>
+                `;
+                responseDiv.classList.add('show');
+            } finally {
+                btn.disabled = false;
+                btn.textContent = 'Send 🚀';
+            }
+        }
+
+        function quickQuery(type) {
+            const input = document.getElementById('askInput');
+            const queries = {
+                'uptime': 'What is the uptime?',
+                'memory': 'Show memory usage',
+                'cpu': 'Show CPU usage',
+                'disk': 'Show disk usage',
+                'ports': 'Show open ports',
+                'processes': 'Show top processes',
+                'help': 'help'
+            };
+            input.value = queries[type] || type;
+            askServer();
+        }
+
+        // Preview functionality
+        let previewTimeout = null;
+        let hideTimeout = null;
+
+        function showPreview(url, title) {
+            clearTimeout(hideTimeout);
+
+            previewTimeout = setTimeout(() => {
+                const overlay = document.getElementById('previewOverlay');
+                const container = document.getElementById('previewContainer');
+                const iframe = document.getElementById('previewIframe');
+                const titleEl = document.getElementById('previewTitle');
+
+                titleEl.textContent = title;
+                iframe.src = url;
+
+                overlay.classList.add('active');
+                container.classList.add('active');
+            }, 500); // Show preview after 500ms hover
+        }
+
+        function hidePreviewDelayed() {
+            clearTimeout(previewTimeout);
+
+            hideTimeout = setTimeout(() => {
+                closePreview();
+            }, 300);
+        }
+
+        function closePreview() {
+            const overlay = document.getElementById('previewOverlay');
+            const container = document.getElementById('previewContainer');
+            const iframe = document.getElementById('previewIframe');
+
+            overlay.classList.remove('active');
+            container.classList.remove('active');
+
+            // Clear iframe after animation
+            setTimeout(() => {
+                if (!container.classList.contains('active')) {
+                    iframe.src = '';
+                }
+            }, 300);
+        }
+
+        // Prevent closing when hovering over preview
+        document.getElementById('previewContainer').addEventListener('mouseenter', () => {
+            clearTimeout(hideTimeout);
+        });
+
+        document.getElementById('previewContainer').addEventListener('mouseleave', () => {
+            closePreview();
+        });
+
+        // Add Directory Modal Functions
+        function openAddDirectoryModal() {
+            document.getElementById('addDirModal').classList.add('active');
+        }
+
+        function closeAddDirectoryModal() {
+            document.getElementById('addDirModal').classList.remove('active');
+            document.getElementById('addDirForm').reset();
+        }
+
+        async function handleAddDirectory(event) {
+            event.preventDefault();
+
+            const directory = document.getElementById('dirPath').value.trim();
+            const port = document.getElementById('dirPort').value.trim();
+            const appName = document.getElementById('dirAppName').value.trim();
+
+            try {
+                const response = await authFetch('/api/add-directory', {
+                    method: 'POST',
+                    headers: {
+                        'Content-Type': 'application/json'
+                    },
+                    body: JSON.stringify({
+                        directory: directory,
+                        port: port || undefined,
+                        appName: appName || undefined
+                    })
+                });
+
+                const result = await response.json();
+
+                if (result.success) {
+                    closeAddDirectoryModal();
+                    alert(`✅ Directory added: ${directory}`);
+                    loadPorts();
+                    loadDirectories();
+                } else {
+                    alert(`❌ Error: ${result.error}`);
+                }
+            } catch (err) {
+                alert(`❌ Error: ${err.message}`);
+            }
+        }
+
+        // Close modal when clicking outside
+        document.getElementById('addDirModal').addEventListener('click', (e) => {
+            if (e.target.id === 'addDirModal') {
+                closeAddDirectoryModal();
+            }
+        });
+
+        // Web View Toggle Functions
+        function toggleWebView(port, url) {
+            const container = document.getElementById(`webview-${port}`);
+            const iframe = document.getElementById(`iframe-${port}`);
+            const button = event.target.closest('.web-view-toggle');
+
+            if (container.classList.contains('active')) {
+                // Hide the web view
+                container.classList.remove('active');
+                button.classList.remove('active');
+                button.innerHTML = '<span>👁️ Show Live Preview</span>';
+                iframe.src = ''; // Clear iframe to stop loading
+            } else {
+                // Show the web view
+                container.classList.add('active');
+                button.classList.add('active');
+                button.innerHTML = '<span>👁️ Hide Live Preview</span>';
+
+                // Load iframe if not already loaded
+                if (!iframe.src) {
+                    iframe.src = url;
+
+                    // Hide loading spinner when iframe loads
+                    iframe.onload = function() {
+                        const loading = container.querySelector('.web-view-loading');
+                        if (loading) loading.style.display = 'none';
+                    };
+
+                    // Handle iframe errors
+                    iframe.onerror = function() {
+                        const loading = container.querySelector('.web-view-loading');
+                        if (loading) {
+                            loading.innerHTML = '<div class="web-view-error">❌ Failed to load preview</div>';
+                        }
+                    };
+                }
+            }
+        }
+
+        function openFullscreen(url, title) {
+            const overlay = document.getElementById('previewOverlay');
+            const container = document.getElementById('previewContainer');
+            const iframe = document.getElementById('previewIframe');
+            const titleEl = document.getElementById('previewTitle');
+
+            titleEl.textContent = title;
+            iframe.src = url;
+
+            overlay.classList.add('active');
+            container.classList.add('active');
+        }
+
+        // Restart app function
+        async function restartApp(directory, port) {
+            if (!confirm(`Are you sure you want to restart the app in ${directory}?`)) {
+                return;
+            }
+
+            const button = event.target;
+            const card = button.closest('.card');
+
+            button.disabled = true;
+            button.innerHTML = '⏳ Starting<span class="loading-spinner"></span>';
+
+            // Add loading state to card
+            card.classList.add('loading');
+            const overlay = document.createElement('div');
+            overlay.className = 'loading-overlay';
+            overlay.innerHTML = `
+                <div class="big-spinner"></div>
+                <div class="loading-text">🚀 Starting app...</div>
+            `;
+            card.appendChild(overlay);
+
+            try {
+                const response = await authFetch(`/api/restart/${encodeURIComponent(directory)}`);
+                const result = await response.json();
+
+                if (result.success) {
+                    overlay.querySelector('.loading-text').textContent = '✅ Started successfully!';
+                    button.innerHTML = '✅ Started!';
+                    button.style.background = 'linear-gradient(90deg, #00b894 0%, #00a085 100%)';
+
+                    // ALWAYS open the URL in a new tab using server IP
+                    const portToOpen = result.lastPort || port;
+                    if (portToOpen) {
+                        setTimeout(() => {
+                            const url = `http://${serverInfo.serverIp}:${portToOpen}`;
+                            window.open(url, '_blank');
+                        }, 1500);
+                    }
+
+                    setTimeout(() => {
+                        card.classList.remove('loading');
+                        overlay.remove();
+                        button.textContent = '🔄 Restart App';
+                        button.style.background = 'linear-gradient(90deg, #ff6b6b 0%, #ee5a6f 100%)';
+                        button.disabled = false;
+
+                        // Reload ports after restart
+                        setTimeout(() => {
+                            loadPorts();
+                            loadDirectories();
+                        }, 2000);
+                    }, 3000);
+                } else {
+                    overlay.querySelector('.loading-text').textContent = '❌ Failed to start';
+                    button.textContent = '❌ Failed';
+                    button.style.background = '#d63031';
+                    setTimeout(() => {
+                        card.classList.remove('loading');
+                        overlay.remove();
+                        button.textContent = '🔄 Restart App';
+                        button.style.background = 'linear-gradient(90deg, #ff6b6b 0%, #ee5a6f 100%)';
+                        button.disabled = false;
+                    }, 3000);
+                    alert(`Failed to restart: ${result.error || result.message}`);
+                }
+            } catch (err) {
+                card.classList.remove('loading');
+                if (overlay && overlay.parentNode) overlay.remove();
+                button.textContent = '❌ Error';
+                button.disabled = false;
+                alert(`Error: ${err.message}`);
+                setTimeout(() => {
+                    button.textContent = '🔄 Restart App';
+                }, 3000);
+            }
+        }
+    </script>
+</body>
+</html>
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..9e31766
--- /dev/null
+++ b/server.js
@@ -0,0 +1,972 @@
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+const { exec } = require('child_process');
+const nodemailer = require('nodemailer');
+const crypto = require('crypto');
+
+const PORT = 8888; // Permanent port assignment
+const SERVER_IP = '45.61.58.125'; // Server public IP address
+const SYSTEM_PORTS = [22, 53, 80, 443, 111, 123, 514]; // Common system ports to exclude
+
+// Authentication
+const AUTH_PASSWORD = 'serveraccess911'; // Change this to your desired password
+const sessions = new Map(); // Store active sessions
+
+// Store directory/port mappings in memory
+const directoryData = {};
+
+// Store previous port states for change detection
+const previousPortStates = {};
+
+// Email configuration
+let emailConfig = {
+    enabled: true,
+    recipient: 'steve@designerwallcoverings.com',
+    smtp: {
+        host: 'localhost',
+        port: 25,
+        secure: false
+    }
+};
+
+// Create email transporter
+let transporter = null;
+try {
+    transporter = nodemailer.createTransport(emailConfig.smtp);
+} catch (err) {
+    console.error('Failed to create email transporter:', err.message);
+}
+
+// Helper functions for authentication
+function generateToken() {
+    return crypto.randomBytes(32).toString('hex');
+}
+
+function isAuthenticated(req) {
+    const authHeader = req.headers['authorization'];
+    if (!authHeader) return false;
+
+    const token = authHeader.replace('Bearer ', '');
+    return sessions.has(token);
+}
+
+function getCookie(req, name) {
+    const cookies = req.headers.cookie;
+    if (!cookies) return null;
+
+    const cookie = cookies.split(';').find(c => c.trim().startsWith(name + '='));
+    return cookie ? cookie.split('=')[1] : null;
+}
+
+const server = http.createServer((req, res) => {
+    // Login endpoint - always accessible
+    if (req.url === '/api/login' && req.method === 'POST') {
+        let body = '';
+        req.on('data', chunk => {
+            body += chunk.toString();
+        });
+        req.on('end', () => {
+            try {
+                const { password } = JSON.parse(body);
+
+                if (password === AUTH_PASSWORD) {
+                    const token = generateToken();
+                    sessions.set(token, { createdAt: Date.now() });
+
+                    res.writeHead(200, { 'Content-Type': 'application/json' });
+                    res.end(JSON.stringify({ success: true, token }));
+                } else {
+                    res.writeHead(401, { 'Content-Type': 'application/json' });
+                    res.end(JSON.stringify({ success: false, error: 'Invalid password' }));
+                }
+            } catch (err) {
+                res.writeHead(400, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({ error: 'Invalid request' }));
+            }
+        });
+        return;
+    }
+
+    // Logout endpoint
+    if (req.url === '/api/logout' && req.method === 'POST') {
+        const authHeader = req.headers['authorization'];
+        if (authHeader) {
+            const token = authHeader.replace('Bearer ', '');
+            sessions.delete(token);
+        }
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({ success: true }));
+        return;
+    }
+
+    // Check authentication for API routes (except login, server-info, suggest-port, domains, and proxy)
+    if (req.url.startsWith('/api/') && req.url !== '/api/server-info' && req.url !== '/api/suggest-port' && req.url !== '/api/domains' && !req.url.startsWith('/api/proxy/') && !isAuthenticated(req)) {
+        res.writeHead(401, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({ error: 'Unauthorized' }));
+        return;
+    }
+
+    if (req.url === '/api/server-info') {
+        // Return server information
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({
+            serverIp: SERVER_IP,
+            port: PORT
+        }));
+        return;
+    }
+
+    if (req.url === '/api/ports') {
+        exec('ss -tulpn 2>/dev/null | grep LISTEN', (error, stdout, stderr) => {
+            if (error && !stdout) {
+                // Fallback to netstat if ss fails
+                exec('netstat -tulpn 2>/dev/null | grep LISTEN', (error2, stdout2, stderr2) => {
+                    if (error2 && !stdout2) {
+                        res.writeHead(500, { 'Content-Type': 'application/json' });
+                        res.end(JSON.stringify({ error: 'Failed to fetch port information' }));
+                        return;
+                    }
+                    enrichPortData(parseNetstat(stdout2), (enrichedPorts) => {
+                        const allServices = mergeWithOfflineDirectories(enrichedPorts);
+                        res.writeHead(200, { 'Content-Type': 'application/json' });
+                        res.end(JSON.stringify(allServices));
+                    });
+                });
+            } else {
+                enrichPortData(parseSS(stdout), (enrichedPorts) => {
+                    const allServices = mergeWithOfflineDirectories(enrichedPorts);
+                    res.writeHead(200, { 'Content-Type': 'application/json' });
+                    res.end(JSON.stringify(allServices));
+                });
+            }
+        });
+    } else if (req.url.startsWith('/api/restart/')) {
+        // Handle restart requests
+        const directory = decodeURIComponent(req.url.replace('/api/restart/', ''));
+        handleRestart(directory, res);
+    } else if (req.url === '/api/directories') {
+        // Get stored directory data
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify(directoryData));
+    } else if (req.url === '/api/domains') {
+        // Get all domains from nginx configs
+        exec('grep -h "server_name" /etc/nginx/sites-enabled/* 2>/dev/null', (error, stdout) => {
+            const domains = [];
+
+            if (stdout) {
+                const lines = stdout.trim().split('\n');
+                lines.forEach(line => {
+                    // Parse server_name lines
+                    const match = line.match(/server_name\s+([^;]+);/);
+                    if (match) {
+                        const serverNames = match[1].trim().split(/\s+/);
+                        serverNames.forEach(domain => {
+                            if (domain && !domain.includes('localhost') && !domain.match(/^\d+\.\d+\.\d+\.\d+$/)) {
+                                domains.push(domain);
+                            }
+                        });
+                    }
+                });
+            }
+
+            // Get unique domains
+            const uniqueDomains = [...new Set(domains)].sort();
+
+            res.writeHead(200, { 'Content-Type': 'application/json' });
+            res.end(JSON.stringify({ domains: uniqueDomains }));
+        });
+    } else if (req.url === '/api/suggest-port') {
+        // Suggest an available port
+        exec('ss -tulpn | grep LISTEN', (error, stdout) => {
+            const usedPorts = new Set();
+
+            // Parse all listening ports
+            const lines = stdout.trim().split('\n');
+            lines.forEach(line => {
+                const match = line.match(/:(\d+)\s/);
+                if (match) {
+                    usedPorts.add(parseInt(match[1]));
+                }
+            });
+
+            // Find available port starting from 9000
+            let suggestedPort = 9000;
+            while (usedPorts.has(suggestedPort)) {
+                suggestedPort++;
+            }
+
+            res.writeHead(200, { 'Content-Type': 'application/json' });
+            res.end(JSON.stringify({ port: suggestedPort }));
+        });
+    } else if (req.url === '/api/scan-directories') {
+        // Scan for directories with package.json - deep scan in specific directories
+        const scanPaths = [
+            'find /root/DW-Websites -name "package.json" -type f 2>/dev/null',
+            'find /root/WebsitesMisc -name "package.json" -type f 2>/dev/null',
+            'find /root/Scripts -name "package.json" -type f 2>/dev/null',
+            'find /opt -name "package.json" -type f 2>/dev/null',
+            'find /root -maxdepth 3 -name "package.json" -type f 2>/dev/null'
+        ];
+
+        const combinedCommand = scanPaths.join(' ; ');
+        exec(combinedCommand, (error, stdout) => {
+            const directories = new Set(); // Use Set to avoid duplicates
+            if (stdout) {
+                const files = stdout.trim().split('\n');
+                files.forEach(file => {
+                    if (file) {
+                        const dir = path.dirname(file);
+                        // Exclude node_modules
+                        if (!dir.includes('node_modules')) {
+                            directories.add(dir);
+                        }
+                    }
+                });
+            }
+
+            const uniqueDirectories = Array.from(directories).sort();
+
+            res.writeHead(200, { 'Content-Type': 'application/json' });
+            res.end(JSON.stringify({
+                directories: uniqueDirectories,
+                count: uniqueDirectories.length
+            }));
+        });
+    } else if (req.url === '/api/add-directory' && req.method === 'POST') {
+        // Handle adding a directory manually
+        let body = '';
+        req.on('data', chunk => {
+            body += chunk.toString();
+        });
+        req.on('end', () => {
+            try {
+                const { directory, port, appName } = JSON.parse(body);
+
+                if (!directory) {
+                    res.writeHead(400, { 'Content-Type': 'application/json' });
+                    res.end(JSON.stringify({ error: 'Directory path is required' }));
+                    return;
+                }
+
+                // Check if directory exists
+                if (!fs.existsSync(directory)) {
+                    res.writeHead(404, { 'Content-Type': 'application/json' });
+                    res.end(JSON.stringify({ error: 'Directory does not exist' }));
+                    return;
+                }
+
+                // Add to directoryData
+                directoryData[directory] = {
+                    lastPort: port || 'N/A',
+                    lastSeen: new Date().toISOString(),
+                    process: 'Unknown',
+                    appName: appName || path.basename(directory)
+                };
+
+                res.writeHead(200, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({
+                    success: true,
+                    message: 'Directory added successfully',
+                    directory: directory
+                }));
+            } catch (err) {
+                res.writeHead(400, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({ error: 'Invalid request body' }));
+            }
+        });
+    } else if (req.url === '/api/ask' && req.method === 'POST') {
+        // Handle natural language server queries
+        let body = '';
+        req.on('data', chunk => {
+            body += chunk.toString();
+        });
+        req.on('end', () => {
+            try {
+                const { query } = JSON.parse(body);
+                handleServerQuery(query, res);
+            } catch (err) {
+                res.writeHead(400, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({ error: 'Invalid request body' }));
+            }
+        });
+    } else if (req.url.startsWith('/api/delete-directory/')) {
+        // Handle deleting a directory
+        const directory = decodeURIComponent(req.url.replace('/api/delete-directory/', ''));
+        if (directoryData[directory]) {
+            delete directoryData[directory];
+            res.writeHead(200, { 'Content-Type': 'application/json' });
+            res.end(JSON.stringify({
+                success: true,
+                message: 'Directory removed successfully'
+            }));
+        } else {
+            res.writeHead(404, { 'Content-Type': 'application/json' });
+            res.end(JSON.stringify({ error: 'Directory not found' }));
+        }
+    } else if (req.url.startsWith('/api/stop-port/') && req.method === 'POST') {
+        // Stop/kill process on port
+        const port = decodeURIComponent(req.url.replace('/api/stop-port/', ''));
+        exec(`lsof -ti:${port} | xargs kill -9 2>/dev/null`, (error, stdout, stderr) => {
+            res.writeHead(200, { 'Content-Type': 'application/json' });
+            res.end(JSON.stringify({
+                success: true,
+                message: `Stopped process on port ${port}`
+            }));
+        });
+    } else if (req.url === '/api/assign-port' && req.method === 'POST') {
+        // Assign port to directory
+        let body = '';
+        req.on('data', chunk => {
+            body += chunk.toString();
+        });
+        req.on('end', () => {
+            try {
+                const { directory, port, appName } = JSON.parse(body);
+
+                if (!directory || !port) {
+                    res.writeHead(400, { 'Content-Type': 'application/json' });
+                    res.end(JSON.stringify({ error: 'Directory and port are required' }));
+                    return;
+                }
+
+                // Update or create directory mapping
+                directoryData[directory] = {
+                    lastPort: port,
+                    lastSeen: new Date().toISOString(),
+                    process: 'Unknown',
+                    appName: appName || path.basename(directory)
+                };
+
+                res.writeHead(200, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({
+                    success: true,
+                    message: `Assigned port ${port} to ${directory}`,
+                    directory: directory,
+                    port: port
+                }));
+            } catch (err) {
+                res.writeHead(400, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({ error: 'Invalid request body' }));
+            }
+        });
+    } else if (req.url === '/api/email-config') {
+        // Get email configuration
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({
+            enabled: emailConfig.enabled,
+            recipient: emailConfig.recipient
+        }));
+    } else if (req.url === '/api/email-config' && req.method === 'POST') {
+        // Update email configuration
+        let body = '';
+        req.on('data', chunk => {
+            body += chunk.toString();
+        });
+        req.on('end', () => {
+            try {
+                const { enabled, recipient } = JSON.parse(body);
+
+                if (enabled !== undefined) emailConfig.enabled = enabled;
+                if (recipient) emailConfig.recipient = recipient;
+
+                res.writeHead(200, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({
+                    success: true,
+                    message: 'Email configuration updated',
+                    config: {
+                        enabled: emailConfig.enabled,
+                        recipient: emailConfig.recipient
+                    }
+                }));
+            } catch (err) {
+                res.writeHead(400, { 'Content-Type': 'application/json' });
+                res.end(JSON.stringify({ error: 'Invalid request body' }));
+            }
+        });
+    } else if (req.url.startsWith('/api/proxy/')) {
+        // Proxy endpoint to bypass X-Frame-Options
+        const targetUrl = decodeURIComponent(req.url.replace('/api/proxy/', ''));
+
+        exec(`curl -sL "${targetUrl}"`, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
+            if (error) {
+                res.writeHead(500, { 'Content-Type': 'text/html' });
+                res.end(`<html><body style="display:flex;align-items:center;justify-content:center;height:100vh;font-family:sans-serif;background:#f0f0f0;"><div style="text-align:center;"><h2>❌ Failed to load</h2><p>${targetUrl}</p><p style="font-size:0.9em;color:#666;">${error.message}</p></div></body></html>`);
+                return;
+            }
+
+            res.writeHead(200, {
+                'Content-Type': 'text/html',
+                'X-Frame-Options': 'ALLOWALL',
+                'Content-Security-Policy': ''
+            });
+            res.end(stdout);
+        });
+    } else if (req.url === '/test') {
+        // Serve test page
+        const filePath = path.join(__dirname, 'test.html');
+        fs.readFile(filePath, (err, data) => {
+            if (err) {
+                res.writeHead(404);
+                res.end('Not found');
+                return;
+            }
+            res.writeHead(200, { 'Content-Type': 'text/html' });
+            res.end(data);
+        });
+    } else {
+        // Serve index.html
+        const filePath = path.join(__dirname, 'public', 'index.html');
+        fs.readFile(filePath, (err, data) => {
+            if (err) {
+                res.writeHead(404);
+                res.end('Not found');
+                return;
+            }
+            res.writeHead(200, { 'Content-Type': 'text/html' });
+            res.end(data);
+        });
+    }
+});
+
+function parseSS(output) {
+    const lines = output.trim().split('\n');
+    const ports = [];
+
+    lines.forEach(line => {
+        const parts = line.trim().split(/\s+/);
+        if (parts.length < 5) return;
+
+        const protocol = parts[0];
+        const localAddress = parts[4];
+        const [address, port] = localAddress.split(':').slice(-2);
+
+        // Extract process info and PID
+        let process = 'Unknown';
+        let pid = null;
+        const usersMatch = line.match(/users:\(\("([^"]+)",pid=(\d+)/);
+        if (usersMatch) {
+            process = usersMatch[1];
+            pid = usersMatch[2];
+        }
+
+        ports.push({
+            port: port || 'N/A',
+            protocol: protocol.toUpperCase(),
+            address: address === '*' ? '0.0.0.0' : address,
+            process: process,
+            pid: pid
+        });
+    });
+
+    return ports.sort((a, b) => parseInt(a.port) - parseInt(b.port));
+}
+
+function parseNetstat(output) {
+    const lines = output.trim().split('\n');
+    const ports = [];
+
+    lines.forEach(line => {
+        const parts = line.trim().split(/\s+/);
+        if (parts.length < 7) return;
+
+        const protocol = parts[0];
+        const localAddress = parts[3];
+        const [address, port] = localAddress.split(':').slice(-2);
+        const processInfo = parts[6] || 'Unknown';
+
+        // Extract PID from process info (format: pid/process)
+        let process = processInfo;
+        let pid = null;
+        const pidMatch = processInfo.match(/(\d+)\/(.*)/);
+        if (pidMatch) {
+            pid = pidMatch[1];
+            process = pidMatch[2];
+        }
+
+        ports.push({
+            port: port || 'N/A',
+            protocol: protocol.toUpperCase(),
+            address: address === '*' ? '0.0.0.0' : address,
+            process: process,
+            pid: pid
+        });
+    });
+
+    return ports.sort((a, b) => parseInt(a.port) - parseInt(b.port));
+}
+
+function enrichPortData(ports, callback) {
+    // Get nginx config data - read actual config files
+    exec('cat /etc/nginx/sites-enabled/* 2>/dev/null', (error, nginxOutput) => {
+        const portMapping = parseNginxConfig(nginxOutput || '');
+
+        // Get Docker data
+        exec('docker ps --format "{{.Names}}|{{.Ports}}|{{.Image}}"', (error, dockerOutput) => {
+            const dockerMapping = parseDockerPs(dockerOutput || '');
+
+            // Get directory info for each port with PID
+            let completed = 0;
+            const enrichedPorts = ports.map(portInfo => {
+                const portNum = portInfo.port;
+                const websites = portMapping[portNum] || [];
+                const docker = dockerMapping[portNum] || null;
+
+                return {
+                    ...portInfo,
+                    websites: websites,
+                    website: websites.length > 0 ? websites.join(', ') : null,
+                    docker: docker,
+                    appName: getAppName(portInfo, websites, docker),
+                    directory: null,
+                    isUserApp: false
+                };
+            });
+
+            // Get directory for each port
+            enrichedPorts.forEach((portInfo, index) => {
+                if (portInfo.pid && !SYSTEM_PORTS.includes(parseInt(portInfo.port))) {
+                    getProcessDirectory(portInfo.pid, (dir) => {
+                        enrichedPorts[index].directory = dir;
+                        enrichedPorts[index].isUserApp = dir && !dir.startsWith('/usr') && !dir.startsWith('/lib') && !dir.startsWith('/bin');
+
+                        // Store directory data
+                        if (dir && enrichedPorts[index].isUserApp) {
+                            directoryData[dir] = {
+                                lastPort: portInfo.port,
+                                lastSeen: new Date().toISOString(),
+                                process: portInfo.process,
+                                appName: portInfo.appName
+                            };
+                        }
+
+                        completed++;
+                        if (completed === enrichedPorts.length) {
+                            callback(enrichedPorts);
+                        }
+                    });
+                } else {
+                    completed++;
+                    if (completed === enrichedPorts.length) {
+                        callback(enrichedPorts);
+                    }
+                }
+            });
+
+            // If no async operations, call immediately
+            if (enrichedPorts.length === 0 || enrichedPorts.every(p => !p.pid || SYSTEM_PORTS.includes(parseInt(p.port)))) {
+                callback(enrichedPorts);
+            }
+        });
+    });
+}
+
+function parseNginxConfig(output) {
+    const mapping = {};
+
+    // Split into server blocks
+    const serverBlocks = output.split(/server\s*{/);
+
+    serverBlocks.forEach(block => {
+        // Find server_name
+        const serverNameMatch = block.match(/server_name\s+([^;]+);/);
+        // Find proxy_pass with port
+        const proxyPassMatch = block.match(/proxy_pass\s+http:\/\/[^:]+:(\d+)/);
+
+        if (serverNameMatch && proxyPassMatch) {
+            const port = proxyPassMatch[1];
+            const domains = serverNameMatch[1]
+                .split(/\s+/)
+                .filter(d => d && d !== '_' && d !== 'www._' && !d.match(/^\d+\.\d+\.\d+\.\d+$/))
+                .map(d => d.replace(/^www\./, '')); // Remove www. prefix
+
+            if (domains.length > 0) {
+                if (!mapping[port]) mapping[port] = [];
+                domains.forEach(domain => {
+                    if (!mapping[port].includes(domain)) {
+                        mapping[port].push(domain);
+                    }
+                });
+            }
+        }
+    });
+
+    return mapping;
+}
+
+function parseDockerPs(output) {
+    const lines = output.trim().split('\n');
+    const mapping = {};
+
+    lines.forEach(line => {
+        const [name, ports, image] = line.split('|');
+        if (!ports) return;
+
+        const portMatch = ports.match(/:(\d+)->/g);
+        if (portMatch) {
+            portMatch.forEach(pm => {
+                const port = pm.match(/:(\d+)->/)[1];
+                mapping[port] = { name, image };
+            });
+        }
+    });
+
+    return mapping;
+}
+
+function getAppName(portInfo, websites, docker) {
+    if (docker) return docker.name;
+    if (websites.length > 0) return websites[0];
+
+    // Guess based on port
+    const portNum = parseInt(portInfo.port);
+    if (portNum === 80 || portNum === 443) return 'Web Server (nginx)';
+    if (portNum === 22) return 'SSH Server';
+    if (portNum === 3000) return 'Node.js App';
+    if (portNum === 8080) return 'HTTP Server';
+    if (portNum === 9000) return 'Node.js App';
+    if (portInfo.process.includes('nginx')) return 'nginx';
+    if (portInfo.process.includes('node')) return 'Node.js App';
+    if (portInfo.process.includes('docker')) return 'Docker Service';
+
+    return portInfo.process;
+}
+
+function getProcessDirectory(pid, callback) {
+    exec(`readlink -f /proc/${pid}/cwd 2>/dev/null`, (error, stdout) => {
+        if (error || !stdout) {
+            callback(null);
+        } else {
+            callback(stdout.trim());
+        }
+    });
+}
+
+function mergeWithOfflineDirectories(activePorts) {
+    const activeDirectories = new Set();
+    const result = [...activePorts];
+
+    // Mark active directories
+    activePorts.forEach(port => {
+        if (port.directory && port.isUserApp) {
+            activeDirectories.add(port.directory);
+        }
+    });
+
+    // Add offline directories
+    Object.keys(directoryData).forEach(directory => {
+        if (!activeDirectories.has(directory)) {
+            const data = directoryData[directory];
+            result.push({
+                port: data.lastPort || 'N/A',
+                protocol: 'N/A',
+                address: 'N/A',
+                process: data.process || 'Unknown',
+                pid: null,
+                websites: [],
+                website: null,
+                docker: null,
+                appName: data.appName || path.basename(directory),
+                directory: directory,
+                isUserApp: true,
+                isOffline: true
+            });
+        }
+    });
+
+    return result;
+}
+
+function handleServerQuery(query, res) {
+    const q = query.toLowerCase().trim();
+
+    let command = '';
+    let type = 'general';
+
+    // Parse the query and determine the command
+    if (q === 'help' || q.includes('help')) {
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({
+            success: true,
+            type: 'help',
+            response: `**Available Commands:**
+- uptime / show uptime
+- memory / memory usage / ram
+- cpu / cpu usage / load
+- disk / disk usage / storage
+- ports / open ports / listening ports
+- processes / top processes
+- directories / my directories
+- network / network info
+- users / logged in users
+- services / systemd services`
+        }));
+        return;
+    }
+
+    if (q.includes('uptime')) {
+        command = 'uptime -p';
+        type = 'uptime';
+    } else if (q.includes('memory') || q.includes('ram')) {
+        command = 'free -h';
+        type = 'memory';
+    } else if (q.includes('cpu') || q.includes('load')) {
+        command = 'top -bn1 | head -20';
+        type = 'cpu';
+    } else if (q.includes('disk') || q.includes('storage')) {
+        command = 'df -h';
+        type = 'disk';
+    } else if (q.includes('port') && (q.includes('open') || q.includes('listening'))) {
+        command = 'ss -tulpn | grep LISTEN';
+        type = 'ports';
+    } else if (q.includes('process') || q.includes('top')) {
+        command = 'ps aux --sort=-%mem | head -15';
+        type = 'processes';
+    } else if (q.includes('director')) {
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({
+            success: true,
+            type: 'directories',
+            response: JSON.stringify(directoryData, null, 2),
+            data: directoryData
+        }));
+        return;
+    } else if (q.includes('network')) {
+        command = 'ip addr show';
+        type = 'network';
+    } else if (q.includes('user') || q.includes('logged in')) {
+        command = 'who';
+        type = 'users';
+    } else if (q.includes('service')) {
+        command = 'systemctl list-units --type=service --state=running';
+        type = 'services';
+    } else if (q.includes('docker')) {
+        command = 'docker ps';
+        type = 'docker';
+    } else {
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({
+            success: false,
+            error: 'I didn\'t understand that question. Type "help" to see available commands.'
+        }));
+        return;
+    }
+
+    // Execute the command
+    exec(command, (error, stdout, stderr) => {
+        if (error) {
+            res.writeHead(200, { 'Content-Type': 'application/json' });
+            res.end(JSON.stringify({
+                success: false,
+                error: stderr || error.message
+            }));
+            return;
+        }
+
+        res.writeHead(200, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({
+            success: true,
+            type: type,
+            query: query,
+            response: stdout.trim()
+        }));
+    });
+}
+
+function handleRestart(directory, res) {
+    console.log(`[RESTART] Request for directory: ${directory}`);
+
+    if (!directory || !directoryData[directory]) {
+        console.log(`[RESTART] Directory not found: ${directory}`);
+        res.writeHead(404, { 'Content-Type': 'application/json' });
+        res.end(JSON.stringify({ error: 'Directory not found' }));
+        return;
+    }
+
+    const data = directoryData[directory];
+    const lastPort = data.lastPort;
+    console.log(`[RESTART] Port: ${lastPort}`);
+
+    // Find package.json or main entry point
+    const packageJsonPath = path.join(directory, 'package.json');
+
+    fs.readFile(packageJsonPath, 'utf8', (err, content) => {
+        let startCommand = 'npm start';
+
+        if (!err) {
+            try {
+                const pkg = JSON.parse(content);
+                if (pkg.scripts && pkg.scripts.start) {
+                    startCommand = 'npm start';
+                } else if (pkg.main) {
+                    startCommand = `node ${pkg.main}`;
+                }
+            } catch (e) {
+                // Fallback to npm start
+            }
+        }
+
+        // Kill existing process on the port first
+        exec(`lsof -ti:${lastPort} | xargs kill -9 2>/dev/null`, () => {
+            // Also kill any node processes running in this directory
+            exec(`ps aux | grep "node.*${directory}" | grep -v grep | awk '{print $2}' | xargs kill -9 2>/dev/null`, () => {
+                // Wait a bit for processes to be killed
+                setTimeout(() => {
+                    // Start the process in the directory with PORT environment variable
+                    const logFile = `/tmp/${path.basename(directory)}.log`;
+
+                    // For Next.js apps, call next directly instead of through npm
+                    let finalCommand = startCommand;
+                    if (startCommand === 'npm start' || startCommand.includes('next')) {
+                        // Check if package.json has next
+                        const pkg = JSON.parse(content);
+                        if (pkg.scripts && pkg.scripts.start && pkg.scripts.start.includes('next')) {
+                            // Call next directly to avoid npm flag issues - use dev mode for instant start
+                            finalCommand = `npx next dev -p ${lastPort}`;
+                        } else {
+                            finalCommand = `PORT=${lastPort} ${startCommand}`;
+                        }
+                    } else {
+                        finalCommand = `PORT=${lastPort} ${startCommand}`;
+                    }
+
+                    // Use bash with NVM to ensure correct Node version
+                    const bashCommand = `bash -c "source ~/.nvm/nvm.sh && cd '${directory}' && ${finalCommand} > ${logFile} 2>&1 &"`;
+                    console.log(`[RESTART] Executing: ${finalCommand}`);
+
+                    exec(bashCommand, (error, stdout, stderr) => {
+                        if (error) {
+                            console.log(`[RESTART] Error: ${error.message}`);
+                            res.writeHead(500, { 'Content-Type': 'application/json' });
+                            res.end(JSON.stringify({
+                                error: 'Failed to restart',
+                                message: stderr || error.message,
+                                logFile: logFile
+                            }));
+                        } else {
+                            console.log(`[RESTART] Command executed successfully`);
+                            // Respond immediately - don't wait
+                            res.writeHead(200, { 'Content-Type': 'application/json' });
+                            res.end(JSON.stringify({
+                                success: true,
+                                message: `Restarted ${path.basename(directory)} on port ${lastPort}`,
+                                directory: directory,
+                                lastPort: lastPort,
+                                openUrl: `http://localhost:${lastPort}`,
+                                logFile: logFile
+                            }));
+                        }
+                    });
+                }, 500); // Reduced wait time
+            });
+        });
+    });
+}
+
+async function sendDownAlert(directory, port, appName) {
+    if (!emailConfig.enabled || !transporter) {
+        console.log(`[Alert] Would send email: ${appName} (${directory}) on port ${port} is DOWN`);
+        return;
+    }
+
+    const mailOptions = {
+        from: '"Port Viewer Alert" <noreply@server.local>',
+        to: emailConfig.recipient,
+        subject: `🚨 ALERT: ${appName || 'Application'} is DOWN`,
+        html: `
+            <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
+                <h2 style="color: #d63031; border-bottom: 3px solid #d63031; padding-bottom: 10px;">
+                    🚨 Port Down Alert
+                </h2>
+                <div style="background: #fff3cd; padding: 20px; border-radius: 10px; margin: 20px 0;">
+                    <h3 style="margin-top: 0;">Application Offline</h3>
+                    <p><strong>Application:</strong> ${appName || 'Unknown'}</p>
+                    <p><strong>Directory:</strong> <code>${directory}</code></p>
+                    <p><strong>Port:</strong> ${port}</p>
+                    <p><strong>Time:</strong> ${new Date().toLocaleString()}</p>
+                </div>
+                <p>The application has stopped responding and is no longer listening on its port.</p>
+                <p style="color: #666; font-size: 12px; margin-top: 30px;">
+                    This is an automated alert from Port Viewer monitoring system.
+                </p>
+            </div>
+        `
+    };
+
+    try {
+        await transporter.sendMail(mailOptions);
+        console.log(`✉️ Email alert sent to ${emailConfig.recipient} for ${appName}`);
+    } catch (error) {
+        console.error(`Failed to send email alert:`, error.message);
+    }
+}
+
+function checkPortChanges(currentPorts) {
+    const currentState = {};
+
+    // Build current state map
+    currentPorts.forEach(port => {
+        if (port.directory && port.isUserApp) {
+            currentState[port.directory] = {
+                isOnline: !port.isOffline,
+                port: port.port,
+                appName: port.appName
+            };
+        }
+    });
+
+    // Check for directories that went offline
+    Object.keys(currentState).forEach(directory => {
+        const current = currentState[directory];
+        const previous = previousPortStates[directory];
+
+        // If we have previous state and it was online but now is offline
+        if (previous && previous.isOnline && !current.isOnline) {
+            console.log(`⚠️ ALERT: ${current.appName} (${directory}) went offline!`);
+            sendDownAlert(directory, current.port, current.appName);
+        }
+    });
+
+    // Also check for directories that completely disappeared
+    Object.keys(previousPortStates).forEach(directory => {
+        if (!currentState[directory] && previousPortStates[directory].isOnline) {
+            const prev = previousPortStates[directory];
+            console.log(`⚠️ ALERT: ${prev.appName} (${directory}) disappeared!`);
+            sendDownAlert(directory, prev.port, prev.appName);
+        }
+    });
+
+    // Update previous state
+    Object.keys(currentState).forEach(directory => {
+        previousPortStates[directory] = currentState[directory];
+    });
+}
+
+// Monitor ports every 60 seconds
+setInterval(() => {
+    exec('ss -tulpn 2>/dev/null | grep LISTEN', (error, stdout, stderr) => {
+        if (error && !stdout) {
+            exec('netstat -tulpn 2>/dev/null | grep LISTEN', (error2, stdout2, stderr2) => {
+                if (!error2 || stdout2) {
+                    enrichPortData(parseNetstat(stdout2), (enrichedPorts) => {
+                        const allServices = mergeWithOfflineDirectories(enrichedPorts);
+                        checkPortChanges(allServices);
+                    });
+                }
+            });
+        } else {
+            enrichPortData(parseSS(stdout), (enrichedPorts) => {
+                const allServices = mergeWithOfflineDirectories(enrichedPorts);
+                checkPortChanges(allServices);
+            });
+        }
+    });
+}, 60000); // Check every 60 seconds
+
+server.listen(PORT, '0.0.0.0', () => {
+    console.log(`🚀 Port Viewer running at http://localhost:${PORT}`);
+    console.log(`📊 View all active ports in your browser`);
+    console.log(`📧 Email alerts enabled for: ${emailConfig.recipient}`);
+    console.log(`⏱️  Port monitoring: Every 60 seconds`);
+    console.log(`🌐 External access: http://${SERVER_IP}:${PORT}`);
+});
diff --git a/test.html b/test.html
new file mode 100644
index 0000000..e722574
--- /dev/null
+++ b/test.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>Port Viewer Test</title>
+</head>
+<body style="background: purple; color: white; padding: 50px; font-size: 24px;">
+    <h1>Port Viewer Test Page</h1>
+    <p>If you can see this, the server is working!</p>
+    <p>Current time: <span id="time"></span></p>
+    <script>
+        document.getElementById('time').textContent = new Date().toLocaleString();
+    </script>
+</body>
+</html>

(oldest)  ·  back to Port Viewer  ·  snapshot: 1 file(s) changed, ~1 modified ee28ffe →