Better encryption for everything, new session login, rewrote a lot of database code changing its storage methods. Prepared for release of 2.0.
This commit is contained in:
12
README.md
12
README.md
@@ -32,16 +32,22 @@ Termix is an open-source forever free self-hosted SSH (other protocols planned,
|
||||
# Features
|
||||
- SSH
|
||||
- Split Screen (Up to 4) & Tab System
|
||||
- User Authentication
|
||||
- Data Persistence
|
||||
|
||||
# Planned Features
|
||||
- Database to Store Connection Details
|
||||
- Organize Hosts (folders, tags, etc.)
|
||||
- VNC
|
||||
- RDP
|
||||
- SFTP (build in file transfer)
|
||||
- ChatGPT/Ollama Integration (for commands)
|
||||
- Login Screen
|
||||
- User Management
|
||||
- Apps (like notes, AI, etc)
|
||||
- Terminal Themes
|
||||
- User Management (roles, permissions, etc.)
|
||||
- SSH Tunneling
|
||||
- More Authentication Methods
|
||||
- Share Hosts
|
||||
- More Security Features (like 2FA, optionally store host passwords, etc.)
|
||||
|
||||
# Installation
|
||||
Visit the Termix [Wiki](https://github.com/LukeGus/Termix/wiki) for information on how to install Termix. You can also use these links to go directly to guide. [Docker](https://github.com/LukeGus/Termix/wiki/Docker) or [Manual](https://github.com/LukeGus/Termix/wiki/Manual).
|
||||
|
||||
551
package-lock.json
generated
551
package-lock.json
generated
@@ -19,6 +19,7 @@
|
||||
"@tiptap/starter-kit": "^2.11.5",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
@@ -1203,6 +1204,71 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/semver": {
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mongodb-js/saslprep": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz",
|
||||
@@ -2590,6 +2656,12 @@
|
||||
"integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@@ -2626,6 +2698,18 @@
|
||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -2643,6 +2727,15 @@
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
@@ -2659,6 +2752,26 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
@@ -2881,7 +2994,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64id": {
|
||||
@@ -2893,6 +3005,20 @@
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
|
||||
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"node-addon-api": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bcrypt-pbkdf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
@@ -2945,7 +3071,6 @@
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
@@ -3107,6 +3232,15 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@@ -3136,13 +3270,27 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
@@ -3519,6 +3667,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -3630,6 +3784,12 @@
|
||||
"react": "^16.8.0 || ^17.0.1 || ^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
@@ -4433,6 +4593,42 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-minipass/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -4487,6 +4683,27 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
||||
@@ -4552,6 +4769,27 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob-parent": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||
@@ -4693,6 +4931,12 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
@@ -4730,6 +4974,19 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
@@ -4778,6 +5035,17 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
@@ -4983,6 +5251,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-generator-function": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz",
|
||||
@@ -5777,7 +6054,6 @@
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
@@ -5786,6 +6062,58 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minipass": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
|
||||
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/minizlib/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/mongodb": {
|
||||
"version": "6.14.2",
|
||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz",
|
||||
@@ -5932,6 +6260,54 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/node-fetch/node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.19",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||
@@ -5983,6 +6359,34 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -6100,6 +6504,15 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
@@ -6223,6 +6636,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -6656,6 +7078,20 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
|
||||
@@ -6771,6 +7207,22 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.32.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.1.tgz",
|
||||
@@ -6998,6 +7450,12 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@@ -7160,6 +7618,12 @@
|
||||
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
||||
@@ -7337,6 +7801,29 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string.prototype.matchall": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
|
||||
@@ -7435,6 +7922,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
||||
@@ -7494,6 +7993,29 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/tar/node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/tiny-invariant": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
||||
@@ -7730,6 +8252,12 @@
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
@@ -7973,6 +8501,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
@@ -7983,6 +8520,12 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
"@tiptap/starter-kit": "^2.11.5",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
|
||||
14
src/App.jsx
14
src/App.jsx
@@ -181,7 +181,7 @@ function App() {
|
||||
|
||||
const handleSaveHost = () => {
|
||||
let hostConfig = {
|
||||
name: addHostForm.name,
|
||||
name: addHostForm.name || addHostForm.ip,
|
||||
ip: addHostForm.ip,
|
||||
user: addHostForm.user,
|
||||
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
|
||||
@@ -273,10 +273,10 @@ function App() {
|
||||
const deleteHost = (hostConfig) => {
|
||||
if (userRef.current) {
|
||||
userRef.current.deleteHost({
|
||||
hostConfig,
|
||||
hostId: hostConfig._id,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateEditHostForm = (hostConfig) => {
|
||||
if (hostConfig) {
|
||||
@@ -289,18 +289,16 @@ function App() {
|
||||
|
||||
const handleEditHost = () => {
|
||||
if (editHostForm.ip && editHostForm.user && ((editHostForm.authMethod === 'password' && editHostForm.password) || (editHostForm.authMethod === 'rsaKey' && editHostForm.rsaKey)) && editHostForm.port && editHostForm.authMethod !== 'Select Auth') {
|
||||
const user = getUser();
|
||||
editHostForm.rememberHost = true;
|
||||
|
||||
if (user && currentHostConfig) {
|
||||
userRef.current.editExistingHost({
|
||||
userId: user.id,
|
||||
if (currentHostConfig) {
|
||||
userRef.current.editHost({
|
||||
oldHostConfig: currentHostConfig,
|
||||
newHostConfig: editHostForm,
|
||||
});
|
||||
setIsEditHostHidden(true);
|
||||
} else {
|
||||
console.error("User or currentHostConfig is null");
|
||||
alert("Host not found");
|
||||
}
|
||||
} else {
|
||||
alert("Please fill out all fields.");
|
||||
|
||||
@@ -27,13 +27,12 @@ function Launchpad({
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
// Close the launchpad when neither form is visible and no error is showing
|
||||
if (
|
||||
launchpadRef.current &&
|
||||
!launchpadRef.current.contains(event.target) &&
|
||||
isAddHostHidden && // Only close if addHost form is hidden
|
||||
isEditHostHidden && // Only close if editHost form is hidden
|
||||
isErrorHidden // Only close if error is hidden
|
||||
isAddHostHidden &&
|
||||
isEditHostHidden &&
|
||||
isErrorHidden
|
||||
) {
|
||||
onClose();
|
||||
}
|
||||
@@ -47,8 +46,8 @@ function Launchpad({
|
||||
}, [onClose, isAddHostHidden, isEditHostHidden, isErrorHidden]);
|
||||
|
||||
const handleEditHostClick = () => {
|
||||
setIsAddHostHidden(false); // Open the form for editing
|
||||
setActiveApp('hostViewer'); // Set active app to HostViewer
|
||||
setIsAddHostHidden(false);
|
||||
setActiveApp('hostViewer');
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -174,10 +173,10 @@ function Launchpad({
|
||||
connectToHost={connectToHost}
|
||||
setIsAddHostHidden={setIsAddHostHidden}
|
||||
deleteHost={deleteHost}
|
||||
editHost={editHost} // Pass editHost here
|
||||
editHost={editHost}
|
||||
createFolder={createFolder}
|
||||
moveHostToFolder={moveHostToFolder}
|
||||
onEditHostClick={handleEditHostClick} // Pass the handler to the form
|
||||
onEditHostClick={handleEditHostClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -63,10 +63,10 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
|
||||
|
||||
const socket = io(
|
||||
window.location.hostname === "localhost"
|
||||
? "http://localhost:8081" // Modified path here
|
||||
? "http://localhost:8081"
|
||||
: "/",
|
||||
{
|
||||
path: "/ssh.io/socket.io", // Same path, no need to modify
|
||||
path: "/ssh.io/socket.io",
|
||||
transports: ["websocket", "polling"],
|
||||
}
|
||||
);
|
||||
|
||||
363
src/User.jsx
363
src/User.jsx
@@ -1,219 +1,237 @@
|
||||
import { useRef, forwardRef, useImperativeHandle } from "react";
|
||||
import { useRef, forwardRef, useImperativeHandle, useEffect } from "react";
|
||||
import io from "socket.io-client";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
let socket;
|
||||
const SOCKET_URL = window.location.hostname === "localhost"
|
||||
? "http://localhost:8082/database.io"
|
||||
: "/database.io";
|
||||
|
||||
if (!socket) {
|
||||
socket = io(
|
||||
window.location.hostname === "localhost"
|
||||
? "http://localhost:8082/database.io"
|
||||
: "/database.io",
|
||||
{
|
||||
path: "/database.io/socket.io",
|
||||
transports: ["websocket", "polling"],
|
||||
}
|
||||
);
|
||||
}
|
||||
const socket = io(SOCKET_URL, {
|
||||
path: "/database.io/socket.io",
|
||||
transports: ["websocket", "polling"],
|
||||
autoConnect: false,
|
||||
});
|
||||
|
||||
export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSuccess, onFailure }, ref) => {
|
||||
export const User = forwardRef(({
|
||||
onLoginSuccess,
|
||||
onCreateSuccess,
|
||||
onDeleteSuccess,
|
||||
onFailure
|
||||
}, ref) => {
|
||||
const socketRef = useRef(socket);
|
||||
const currentUser = useRef(null);
|
||||
|
||||
const createUser = (userConfig) => {
|
||||
if (socketRef.current) {
|
||||
socketRef.current.emit("createUser", {
|
||||
username: userConfig.username,
|
||||
password: userConfig.password,
|
||||
useEffect(() => {
|
||||
socketRef.current.connect();
|
||||
return () => socketRef.current.disconnect();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const verifySession = async () => {
|
||||
const storedSession = localStorage.getItem("sessionToken");
|
||||
if (!storedSession || storedSession === "undefined") return;
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("verifySession", { sessionToken: storedSession }, resolve);
|
||||
});
|
||||
|
||||
if (response?.success) {
|
||||
currentUser.current = {
|
||||
id: response.user.id,
|
||||
username: response.user.username,
|
||||
sessionToken: storedSession,
|
||||
};
|
||||
onLoginSuccess(response.user);
|
||||
} else {
|
||||
localStorage.removeItem("sessionToken");
|
||||
onFailure("Session expired");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
verifySession();
|
||||
}, []);
|
||||
|
||||
const createUser = async (userConfig) => {
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("createUser", userConfig, resolve);
|
||||
});
|
||||
|
||||
socketRef.current.once("userCreated", (data) => {
|
||||
if (response?.user?.sessionToken) {
|
||||
currentUser.current = {
|
||||
id: data.user._id,
|
||||
username: data.user.username,
|
||||
sessionToken: data.user.sessionToken,
|
||||
id: response.user.id,
|
||||
username: response.user.username,
|
||||
sessionToken: response.user.sessionToken,
|
||||
};
|
||||
localStorage.setItem('sessionToken', data.user.sessionToken);
|
||||
onCreateSuccess(data);
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
console.error(error);
|
||||
const errorMsg = (error && typeof error === 'object' && error !== null)
|
||||
? error.error || error.message || 'An error occurred'
|
||||
: String(error);
|
||||
onFailure(errorMsg);
|
||||
});
|
||||
localStorage.setItem("sessionToken", response.user.sessionToken);
|
||||
onCreateSuccess(response.user);
|
||||
} else {
|
||||
throw new Error(response?.error || "User creation failed");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const loginUser = (userConfig) => {
|
||||
if (socketRef.current) {
|
||||
setTimeout(() => {
|
||||
socketRef.current.emit("loginUser", {
|
||||
username: userConfig.username,
|
||||
password: userConfig.password,
|
||||
sessionToken: userConfig.sessionToken,
|
||||
});
|
||||
const loginUser = async ({ username, password, sessionToken }) => {
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
const credentials = sessionToken ? { sessionToken } : { username, password };
|
||||
socketRef.current.emit("loginUser", credentials, resolve);
|
||||
});
|
||||
|
||||
socketRef.current.once("userFound", (data) => {
|
||||
currentUser.current = {
|
||||
id: data._id,
|
||||
username: data.username,
|
||||
sessionToken: data.sessionToken,
|
||||
};
|
||||
localStorage.setItem('sessionToken', data.sessionToken);
|
||||
onLoginSuccess(data);
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
console.error(error);
|
||||
const errorMsg = (error && typeof error === 'object' && error !== null)
|
||||
? error.error || error.message || 'An error occurred'
|
||||
: String(error);
|
||||
onFailure(errorMsg);
|
||||
});
|
||||
}, 500);
|
||||
if (response?.success) {
|
||||
currentUser.current = {
|
||||
id: response.user.id,
|
||||
username: response.user.username,
|
||||
sessionToken: response.user.sessionToken,
|
||||
};
|
||||
localStorage.setItem("sessionToken", response.user.sessionToken);
|
||||
onLoginSuccess(response.user);
|
||||
} else {
|
||||
throw new Error(response?.error || "Login failed");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const logoutUser = () => {
|
||||
localStorage.removeItem('sessionToken');
|
||||
localStorage.removeItem("sessionToken");
|
||||
currentUser.current = null;
|
||||
onLoginSuccess(null);
|
||||
};
|
||||
|
||||
const deleteUser = () => {
|
||||
if (currentUser.current?.id && socketRef.current) {
|
||||
socketRef.current.emit("deleteUser", {
|
||||
userId: currentUser.current.id,
|
||||
const deleteUser = async () => {
|
||||
if (!currentUser.current) return onFailure("No user logged in");
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("deleteUser", {
|
||||
userId: currentUser.current.id,
|
||||
sessionToken: currentUser.current.sessionToken,
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
socketRef.current.once("userDeleted", (data) => {
|
||||
onDeleteSuccess(data);
|
||||
currentUser.current = null;
|
||||
localStorage.removeItem('sessionToken');
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
console.error(error);
|
||||
const errorMsg = (error && typeof error === 'object' && error !== null)
|
||||
? error.error || error.message || 'An error occurred'
|
||||
: String(error);
|
||||
onFailure(errorMsg);
|
||||
});
|
||||
} else {
|
||||
onFailure("No user is currently logged in.");
|
||||
if (response?.success) {
|
||||
logoutUser();
|
||||
onDeleteSuccess(response);
|
||||
} else {
|
||||
throw new Error(response?.error || "User deletion failed");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
const saveHost = (hostConfig) => {
|
||||
if (currentUser.current?.id && socketRef.current) {
|
||||
socketRef.current.emit("saveHostConfig", {
|
||||
userId: currentUser.current.id,
|
||||
hostConfig: hostConfig,
|
||||
const saveHost = async (hostConfig) => {
|
||||
if (!currentUser.current) return onFailure("Not authenticated");
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("saveHostConfig", {
|
||||
userId: currentUser.current.id,
|
||||
sessionToken: currentUser.current.sessionToken,
|
||||
...hostConfig
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
onFailure(error);
|
||||
});
|
||||
} else {
|
||||
onFailure("No user is currently logged in.");
|
||||
if (!response?.success) {
|
||||
throw new Error(response?.error || "Failed to save host");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getUser = () => {
|
||||
return currentUser.current;
|
||||
}
|
||||
const getAllHosts = async () => {
|
||||
if (!currentUser.current) return [];
|
||||
|
||||
const getAllHosts = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (currentUser.current?.id && socketRef.current) {
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("getHosts", {
|
||||
userId: currentUser.current.id,
|
||||
});
|
||||
sessionToken: currentUser.current.sessionToken,
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
socketRef.current.once("hostsFound", (data) => {
|
||||
if (data && Array.isArray(data)) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject("Invalid data received.");
|
||||
}
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
console.error(error);
|
||||
const errorMsg = (error && typeof error === 'object' && error !== null)
|
||||
? error.error || error.message || 'An error occurred'
|
||||
: String(error);
|
||||
reject(errorMsg);
|
||||
});
|
||||
if (response?.success) {
|
||||
return response.hosts;
|
||||
} else {
|
||||
reject("No user is currently logged in.");
|
||||
throw new Error(response?.error || "Failed to fetch hosts");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteHost = (hostConfig) => {
|
||||
if (currentUser.current?.id && socketRef.current) {
|
||||
socketRef.current.emit("deleteHost", {
|
||||
userId: currentUser.current.id,
|
||||
hostConfig: hostConfig,
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
onFailure(error);
|
||||
});
|
||||
} else {
|
||||
onFailure("No user is currently logged in.");
|
||||
}
|
||||
}
|
||||
|
||||
const editExistingHost = ({ userId, oldHostConfig, newHostConfig }) => {
|
||||
if (currentUser.current?.id && socketRef.current) {
|
||||
socketRef.current.emit("editHost", {
|
||||
userId: userId,
|
||||
oldHostConfig: oldHostConfig,
|
||||
newHostConfig: newHostConfig,
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
onFailure(error);
|
||||
});
|
||||
} else {
|
||||
onFailure("No user is currently logged in.");
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const createFolder = (folderName) => {
|
||||
if (currentUser.current?.id && socketRef.current) {
|
||||
socketRef.current.emit("createFolder", {
|
||||
userId: currentUser.current.id,
|
||||
folderName: folderName,
|
||||
const deleteHost = async ({ hostId }) => {
|
||||
if (!currentUser.current) return onFailure("Not authenticated");
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("deleteHost", {
|
||||
userId: currentUser.current.id,
|
||||
sessionToken: currentUser.current.sessionToken,
|
||||
hostId: hostId,
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
onFailure(error);
|
||||
});
|
||||
} else {
|
||||
onFailure("No user is currently logged in.");
|
||||
if (!response?.success) {
|
||||
throw new Error(response?.error || "Failed to delete host");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const moveHostToFolder = (folderName, hostConfig) => {
|
||||
if (currentUser.current?.id && socketRef.current) {
|
||||
socketRef.current.emit("moveHostToFolder", {
|
||||
userId: currentUser.current.id,
|
||||
folderName: folderName,
|
||||
hostConfig: hostConfig,
|
||||
const editHost = async ({ oldHostConfig, newHostConfig }) => {
|
||||
if (!currentUser.current) return onFailure("Not authenticated");
|
||||
|
||||
try {
|
||||
console.log('Editing host with configs:', { oldHostConfig, newHostConfig });
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("editHost", {
|
||||
userId: currentUser.current.id,
|
||||
sessionToken: currentUser.current.sessionToken,
|
||||
oldHostConfig,
|
||||
newHostConfig,
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
socketRef.current.once("error", (error) => {
|
||||
onFailure(error);
|
||||
});
|
||||
} else {
|
||||
onFailure("No user is currently logged in.");
|
||||
if (!response?.success) {
|
||||
throw new Error(response?.error || "Failed to edit host");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const shareHost = async (hostId, targetUsername) => {
|
||||
if (!currentUser.current) return onFailure("Not authenticated");
|
||||
|
||||
try {
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("shareHost", {
|
||||
userId: currentUser.current.id,
|
||||
sessionToken: currentUser.current.sessionToken,
|
||||
hostId,
|
||||
targetUsername,
|
||||
}, resolve);
|
||||
});
|
||||
|
||||
if (!response?.success) {
|
||||
throw new Error(response?.error || "Failed to share host");
|
||||
}
|
||||
} catch (error) {
|
||||
onFailure(error.message);
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
createUser,
|
||||
@@ -221,15 +239,14 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
|
||||
logoutUser,
|
||||
deleteUser,
|
||||
saveHost,
|
||||
getUser,
|
||||
getAllHosts,
|
||||
deleteHost,
|
||||
editExistingHost,
|
||||
createFolder,
|
||||
moveHostToFolder,
|
||||
shareHost,
|
||||
editHost,
|
||||
getUser: () => currentUser.current,
|
||||
}));
|
||||
|
||||
return <div></div>;
|
||||
return null;
|
||||
});
|
||||
|
||||
User.displayName = "User";
|
||||
|
||||
@@ -4,46 +4,38 @@ import { Button } from "@mui/joy";
|
||||
|
||||
function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, editHost }) {
|
||||
const [hosts, setHosts] = useState([]);
|
||||
const [initialLoadComplete, setInitialLoadComplete] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const isMounted = useRef(true);
|
||||
|
||||
const fetchHosts = async () => {
|
||||
try {
|
||||
const savedHosts = await getHosts();
|
||||
if (isMounted.current) {
|
||||
setHosts(savedHosts || []);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Host fetch failed:", error);
|
||||
if (isMounted.current) {
|
||||
setHosts([]);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
fetchHosts();
|
||||
|
||||
async function fetchInitialHosts() {
|
||||
try {
|
||||
const savedHosts = await getHosts();
|
||||
if (isMounted.current) {
|
||||
setHosts(savedHosts || []);
|
||||
setInitialLoadComplete(true);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Initial host fetch failed:", error);
|
||||
if (isMounted.current) {
|
||||
setHosts([]);
|
||||
setInitialLoadComplete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchInitialHosts();
|
||||
|
||||
const intervalId = setInterval(async () => {
|
||||
try {
|
||||
const savedHosts = await getHosts();
|
||||
if (isMounted.current) {
|
||||
setHosts(savedHosts || []);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Periodic host update failed:", error);
|
||||
}
|
||||
const intervalId = setInterval(() => {
|
||||
fetchHosts();
|
||||
}, 2000);
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
clearInterval(intervalId);
|
||||
};
|
||||
}, [getHosts]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full p-4 text-white flex flex-col">
|
||||
@@ -61,25 +53,29 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex-grow overflow-auto">
|
||||
{hosts.length > 0 ? (
|
||||
{isLoading ? (
|
||||
<p className="text-gray-300">Loading hosts...</p>
|
||||
) : hosts.length > 0 ? (
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
{hosts.map((hostWrapper, index) => {
|
||||
const hostConfig = hostWrapper.hostConfig || {};
|
||||
const hostConfig = hostWrapper.config || {};
|
||||
|
||||
if (!hostConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={index} className="flex justify-between items-center bg-neutral-800 p-3 rounded-lg shadow-md border border-neutral-700 w-full">
|
||||
<div>
|
||||
<p className="font-semibold">{hostConfig.name || hostConfig.ip}</p>
|
||||
<p className="text-sm text-gray-400">
|
||||
{hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : hostConfig.ip}:{hostConfig.port}
|
||||
{hostConfig.user ? `${hostConfig.user}@${hostConfig.ip}` : `${hostConfig.ip}:${hostConfig.port}`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
className="text-black"
|
||||
onClick={() => {
|
||||
connectToHost(hostConfig);
|
||||
}}
|
||||
onClick={() => connectToHost(hostConfig)}
|
||||
sx={{
|
||||
backgroundColor: "#6e6e6e",
|
||||
"&:hover": { backgroundColor: "#0f0f0f" }
|
||||
@@ -90,7 +86,7 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
||||
<Button
|
||||
className="text-black"
|
||||
onClick={() => {
|
||||
deleteHost(hostConfig);
|
||||
deleteHost({ ...hostConfig, _id: hostWrapper._id });
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor: "#6e6e6e",
|
||||
@@ -117,7 +113,7 @@ function HostViewer({ getHosts, connectToHost, setIsAddHostHidden, deleteHost, e
|
||||
})}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-300">Hosts are either loading or do not exist...</p>
|
||||
<p className="text-gray-300">No hosts available...</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,341 +1,385 @@
|
||||
const http = require("http");
|
||||
const socketIo = require("socket.io");
|
||||
const mongoose = require("mongoose");
|
||||
const http = require('http');
|
||||
const socketIo = require('socket.io');
|
||||
const mongoose = require('mongoose');
|
||||
const bcrypt = require('bcrypt');
|
||||
const crypto = require('crypto');
|
||||
require('dotenv').config();
|
||||
|
||||
const logger = {
|
||||
info: (...args) => console.log(`🔧 [${new Date().toISOString()}] INFO:`, ...args),
|
||||
error: (...args) => console.error(`❌ [${new Date().toISOString()}] ERROR:`, ...args),
|
||||
warn: (...args) => console.warn(`⚠️ [${new Date().toISOString()}] WARN:`, ...args),
|
||||
debug: (...args) => console.debug(`🔍 [${new Date().toISOString()}] DEBUG:`, ...args)
|
||||
};
|
||||
|
||||
const server = http.createServer();
|
||||
const io = socketIo(server, {
|
||||
path: "/database.io/socket.io",
|
||||
cors: {
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
},
|
||||
allowEIO3: true
|
||||
path: '/database.io/socket.io',
|
||||
cors: { origin: '*', methods: ['GET', 'POST'] }
|
||||
});
|
||||
|
||||
const dbNamespace = io.of("/database.io");
|
||||
|
||||
async function connectToMongoDB() {
|
||||
try {
|
||||
const mongoUrl = process.env.MONGO_URL || 'mongodb://mongodb:27017/termix';
|
||||
await mongoose.connect(mongoUrl, {});
|
||||
console.log('Connected to MongoDB');
|
||||
|
||||
const db = mongoose.connection.db;
|
||||
|
||||
// Create the 'users' collection if it doesn't exist
|
||||
const collections = await db.listCollections().toArray();
|
||||
if (!collections.find(col => col.name === 'users')) {
|
||||
await db.createCollection('users');
|
||||
console.log('Successfully created collection: users');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error connecting to MongoDB:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const userSchema = new mongoose.Schema({
|
||||
username: { type: String, required: true, unique: true },
|
||||
password: { type: String, required: true },
|
||||
sessionToken: { type: String, required: true },
|
||||
sshConnections: { type: [Object], default: [] },
|
||||
sessionToken: { type: String, required: true }
|
||||
});
|
||||
|
||||
const hostSchema = new mongoose.Schema({
|
||||
name: { type: String, required: true },
|
||||
config: { type: String, required: true },
|
||||
users: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }],
|
||||
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
|
||||
});
|
||||
|
||||
const User = mongoose.model('User', userSchema);
|
||||
const Host = mongoose.model('Host', hostSchema);
|
||||
|
||||
async function createUser(username, password) {
|
||||
const getEncryptionKey = (userId, sessionToken) => {
|
||||
return crypto.scryptSync(`${userId}-${sessionToken}`, 'salt', 32);
|
||||
};
|
||||
|
||||
const encryptData = (data, userId, sessionToken) => {
|
||||
try {
|
||||
const userExists = await User.findOne({ username });
|
||||
if (userExists) {
|
||||
return { error: "User already exists for username" };
|
||||
}
|
||||
|
||||
const sessionToken = crypto.randomBytes(64).toString('hex');
|
||||
const newUser = new User({ username, password, sessionToken });
|
||||
await newUser.save();
|
||||
return { success: true, user: { _id: newUser._id, username: newUser.username, sessionToken: newUser.sessionToken } };
|
||||
} catch (err) {
|
||||
return { error: 'Error creating user: ' + err.message };
|
||||
const iv = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv('aes-256-gcm', getEncryptionKey(userId, sessionToken), iv);
|
||||
const encrypted = Buffer.concat([cipher.update(JSON.stringify(data)), cipher.final()]);
|
||||
return `${iv.toString('hex')}:${encrypted.toString('hex')}:${cipher.getAuthTag().toString('hex')}`;
|
||||
} catch (error) {
|
||||
logger.error('Encryption failed:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function loginUser(username, password) {
|
||||
const decryptData = (encryptedData, userId, sessionToken) => {
|
||||
try {
|
||||
const user = await User.findOne({ username, password });
|
||||
if (user) {
|
||||
if (!user.sessionToken) {
|
||||
user.sessionToken = crypto.randomBytes(64).toString('hex');
|
||||
await user.save();
|
||||
const [ivHex, contentHex, authTagHex] = encryptedData.split(':');
|
||||
const iv = Buffer.from(ivHex, 'hex');
|
||||
const content = Buffer.from(contentHex, 'hex');
|
||||
const authTag = Buffer.from(authTagHex, 'hex');
|
||||
|
||||
const decipher = crypto.createDecipheriv('aes-256-gcm', getEncryptionKey(userId, sessionToken), iv);
|
||||
decipher.setAuthTag(authTag);
|
||||
|
||||
return JSON.parse(Buffer.concat([decipher.update(content), decipher.final()]).toString());
|
||||
} catch (error) {
|
||||
logger.error('Decryption failed:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
mongoose.connect(process.env.MONGO_URL || 'mongodb://localhost:27017/termix')
|
||||
.then(() => logger.info('Connected to MongoDB'))
|
||||
.catch(err => logger.error('MongoDB connection error:', err));
|
||||
|
||||
io.of('/database.io').on('connection', (socket) => {
|
||||
socket.on('createUser', async ({ username, password }, callback) => {
|
||||
try {
|
||||
logger.debug(`Creating user: ${username}`);
|
||||
|
||||
if (await User.exists({ username })) {
|
||||
logger.warn(`Username already exists: ${username}`);
|
||||
return callback({ error: 'Username already exists' });
|
||||
}
|
||||
return {
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
sessionToken: user.sessionToken,
|
||||
};
|
||||
} else {
|
||||
return { error: 'User not found or incorrect credentials for username' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error checking user: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function loginWithToken(sessionToken) {
|
||||
try {
|
||||
const user = await User.findOne({ sessionToken });
|
||||
if (user) {
|
||||
return {
|
||||
_id: user._id,
|
||||
username: user.username,
|
||||
sessionToken: user.sessionToken,
|
||||
};
|
||||
} else {
|
||||
return { error: 'Invalid session token' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error checking session token: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteUser(userId) {
|
||||
try {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
await User.deleteOne({ _id: userId });
|
||||
return { success: true };
|
||||
} else {
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error removing user: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function saveHostConfig(userId, hostConfig) {
|
||||
try {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
user.sshConnections.push(hostConfig);
|
||||
await user.save();
|
||||
return { success: true };
|
||||
} else {
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error saving host config: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function getHosts(userId) {
|
||||
try {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
return user.sshConnections;
|
||||
} else {
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error getting hosts: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteHost(userId, hostConfig) {
|
||||
try {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
user.sshConnections = user.sshConnections.filter(connection => {
|
||||
const matches =
|
||||
connection.name === hostConfig.name &&
|
||||
connection.ip === hostConfig.ip &&
|
||||
connection.port === hostConfig.port &&
|
||||
connection.user === hostConfig.user;
|
||||
|
||||
return !matches;
|
||||
const sessionToken = crypto.randomBytes(64).toString('hex');
|
||||
const user = await User.create({
|
||||
username,
|
||||
password: await bcrypt.hash(password, 10),
|
||||
sessionToken
|
||||
});
|
||||
|
||||
await user.save();
|
||||
return { success: true };
|
||||
} else {
|
||||
return { error: 'User not found' };
|
||||
logger.info(`User created: ${username}`);
|
||||
callback({ success: true, user: {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
sessionToken
|
||||
}});
|
||||
} catch (error) {
|
||||
logger.error('User creation error:', error);
|
||||
callback({ error: 'User creation failed' });
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error deleting host: ' + err.message };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function editHost(userId, oldHostConfig, newHostConfig) {
|
||||
try {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
user.sshConnections = user.sshConnections.map(connection => {
|
||||
const matches =
|
||||
connection.hostConfig.name === oldHostConfig.name &&
|
||||
connection.hostConfig.ip === oldHostConfig.ip &&
|
||||
connection.hostConfig.port === oldHostConfig.port &&
|
||||
connection.hostConfig.user === oldHostConfig.user;
|
||||
|
||||
if (matches) {
|
||||
return { hostConfig: newHostConfig };
|
||||
} else {
|
||||
return connection;
|
||||
}
|
||||
});
|
||||
|
||||
await user.save();
|
||||
return { success: true };
|
||||
} else {
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error editing host: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function createFolder(userId, folderName) {
|
||||
try {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
user.sshConnections.push({ folderName, connections: [] });
|
||||
await user.save();
|
||||
return { success: true };
|
||||
} else {
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error creating folder: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
async function moveHostToFolder(userId, hostConfig, folderName) {
|
||||
try {
|
||||
const user = await User.findById(userId);
|
||||
if (user) {
|
||||
const folder = user.sshConnections.find(folder => folder.folderName === folderName);
|
||||
if (folder) {
|
||||
folder.connections.push(hostConfig);
|
||||
await user.save();
|
||||
return { success: true };
|
||||
socket.on('loginUser', async ({ username, password, sessionToken }, callback) => {
|
||||
try {
|
||||
let user;
|
||||
if (sessionToken) {
|
||||
user = await User.findOne({ sessionToken });
|
||||
} else {
|
||||
return { error: 'Folder not found' };
|
||||
user = await User.findOne({ username });
|
||||
if (!user || !(await bcrypt.compare(password, user.password))) {
|
||||
logger.warn(`Invalid credentials for: ${username}`);
|
||||
return callback({ error: 'Invalid credentials' });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return { error: 'User not found' };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'Error moving host to folder: ' + err.message };
|
||||
}
|
||||
}
|
||||
|
||||
dbNamespace.on("connection", (socket) => {
|
||||
console.log("New socket connection established on");
|
||||
if (!user) {
|
||||
logger.warn('Login failed - user not found');
|
||||
return callback({ error: 'Invalid credentials' });
|
||||
}
|
||||
|
||||
socket.on("createUser", async (data) => {
|
||||
const { username, password } = data;
|
||||
if (!username || !password) {
|
||||
socket.emit("error", "Please provide both username and password");
|
||||
return;
|
||||
logger.info(`User logged in: ${user.username}`);
|
||||
callback({ success: true, user: {
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
sessionToken: user.sessionToken
|
||||
}});
|
||||
} catch (error) {
|
||||
logger.error('Login error:', error);
|
||||
callback({ error: 'Login failed' });
|
||||
}
|
||||
const result = await createUser(username, password);
|
||||
socket.emit(result.error ? "error" : "userCreated", result);
|
||||
console.log(result.error || `User created`);
|
||||
});
|
||||
|
||||
socket.on("loginUser", async (data) => {
|
||||
const { username, password, sessionToken } = data;
|
||||
let result;
|
||||
if (sessionToken) {
|
||||
result = await loginWithToken(sessionToken);
|
||||
} else if (username && password) {
|
||||
result = await loginUser(username, password);
|
||||
} else {
|
||||
socket.emit("error", "Please provide both username and password or a session token");
|
||||
return;
|
||||
socket.on('saveHostConfig', async ({ userId, sessionToken, hostConfig }, callback) => {
|
||||
try {
|
||||
if (!userId || !sessionToken) {
|
||||
logger.warn('Missing authentication parameters');
|
||||
return callback({ error: 'Authentication required' });
|
||||
}
|
||||
|
||||
if (!hostConfig || typeof hostConfig !== 'object') {
|
||||
logger.warn('Invalid host config format');
|
||||
return callback({ error: 'Invalid host configuration' });
|
||||
}
|
||||
|
||||
if (!hostConfig.ip || !hostConfig.user) {
|
||||
logger.warn('Missing required fields:', hostConfig);
|
||||
return callback({ error: 'IP and User are required' });
|
||||
}
|
||||
|
||||
const user = await User.findOne({ _id: userId, sessionToken });
|
||||
if (!user) {
|
||||
logger.warn(`Invalid session for user: ${userId}`);
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
const cleanConfig = {
|
||||
name: hostConfig.name.trim(),
|
||||
ip: hostConfig.ip.trim(),
|
||||
user: hostConfig.user.trim(),
|
||||
port: hostConfig.port || 22,
|
||||
password: hostConfig.password?.trim() || undefined,
|
||||
rsaKey: hostConfig.rsaKey?.trim() || undefined
|
||||
};
|
||||
|
||||
const finalName = cleanConfig.name || cleanConfig.ip;
|
||||
|
||||
const existingHost = await Host.findOne({
|
||||
name: finalName,
|
||||
createdBy: userId
|
||||
});
|
||||
|
||||
if (existingHost) {
|
||||
logger.warn(`Host with name ${finalName} already exists for user: ${userId}`);
|
||||
return callback({ error: 'Host with this name already exists' });
|
||||
}
|
||||
|
||||
const encryptedConfig = encryptData(cleanConfig, userId, sessionToken);
|
||||
if (!encryptedConfig) {
|
||||
logger.error('Encryption failed for host config');
|
||||
return callback({ error: 'Configuration encryption failed' });
|
||||
}
|
||||
|
||||
await Host.create({
|
||||
name: finalName,
|
||||
config: encryptedConfig,
|
||||
users: [userId],
|
||||
createdBy: userId
|
||||
});
|
||||
|
||||
logger.info(`Host created successfully: ${finalName}`);
|
||||
callback({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Host save error:', error);
|
||||
callback({ error: `Host save failed: ${error.message}` });
|
||||
}
|
||||
socket.emit(result.error ? "error" : "userFound", result);
|
||||
console.log(result.error || `User logged in`);
|
||||
});
|
||||
|
||||
socket.on("deleteUser", async (data) => {
|
||||
const { userId } = data;
|
||||
if (!userId) {
|
||||
socket.emit("error", "User ID is required");
|
||||
return;
|
||||
socket.on('getHosts', async ({ userId, sessionToken }, callback) => {
|
||||
try {
|
||||
const user = await User.findOne({ _id: userId, sessionToken });
|
||||
if (!user) {
|
||||
logger.warn(`Invalid session for user: ${userId}`);
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
const hosts = await Host.find({ users: userId });
|
||||
const decryptedHosts = hosts.map(host => ({
|
||||
...host.toObject(),
|
||||
config: decryptData(host.config, userId, sessionToken)
|
||||
})).filter(host => host.config);
|
||||
|
||||
callback({ success: true, hosts: decryptedHosts });
|
||||
} catch (error) {
|
||||
logger.error('Get hosts error:', error);
|
||||
callback({ error: 'Failed to fetch hosts' });
|
||||
}
|
||||
const result = await deleteUser(userId);
|
||||
socket.emit(result.error ? "error" : "userDeleted", result);
|
||||
console.log(result.error || `User deleted`);
|
||||
});
|
||||
|
||||
socket.on("saveHostConfig", async (data) => {
|
||||
const { userId, hostConfig } = data;
|
||||
if (!userId || !hostConfig) {
|
||||
socket.emit("error", "User ID and host config are required");
|
||||
return;
|
||||
socket.on('deleteHost', async ({ userId, sessionToken, hostId }, callback) => {
|
||||
try {
|
||||
logger.debug(`Deleting host: ${hostId} for user: ${userId}`);
|
||||
|
||||
if (!userId || !sessionToken) {
|
||||
logger.warn('Missing authentication parameters');
|
||||
return callback({ error: 'Authentication required' });
|
||||
}
|
||||
|
||||
if (!hostId || typeof hostId !== 'string') {
|
||||
logger.warn('Invalid host ID format');
|
||||
return callback({ error: 'Invalid host ID' });
|
||||
}
|
||||
|
||||
const user = await User.findOne({ _id: userId, sessionToken });
|
||||
if (!user) {
|
||||
logger.warn(`Invalid session for user: ${userId}`);
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
const result = await Host.deleteOne({ _id: hostId, createdBy: userId });
|
||||
if (result.deletedCount === 0) {
|
||||
logger.warn(`Host not found or not authorized: ${hostId}`);
|
||||
return callback({ error: 'Host not found or not authorized' });
|
||||
}
|
||||
|
||||
logger.info(`Host deleted: ${hostId}`);
|
||||
callback({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Host deletion error:', error);
|
||||
callback({ error: `Host deletion failed: ${error.message}` });
|
||||
}
|
||||
const result = await saveHostConfig(userId, hostConfig);
|
||||
socket.emit(result.error ? "error" : "hostConfigSaved", result);
|
||||
console.log(result.error || `Host config saved`);
|
||||
});
|
||||
|
||||
socket.on("getHosts", async (data) => {
|
||||
const { userId } = data;
|
||||
if (!userId) {
|
||||
socket.emit("error", "User ID is required");
|
||||
return;
|
||||
socket.on('shareHost', async ({ userId, sessionToken, hostId, targetUsername }, callback) => {
|
||||
try {
|
||||
logger.debug(`Sharing host ${hostId} with ${targetUsername}`);
|
||||
|
||||
const user = await User.findOne({ _id: userId, sessionToken });
|
||||
if (!user) {
|
||||
logger.warn(`Invalid session for user: ${userId}`);
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
const targetUser = await User.findOne({ username: targetUsername });
|
||||
if (!targetUser) {
|
||||
logger.warn(`Target user not found: ${targetUsername}`);
|
||||
return callback({ error: 'User not found' });
|
||||
}
|
||||
|
||||
const host = await Host.findOne({ _id: hostId, createdBy: userId });
|
||||
if (!host) {
|
||||
logger.warn(`Host not found or unauthorized: ${hostId}`);
|
||||
return callback({ error: 'Host not found' });
|
||||
}
|
||||
|
||||
if (host.users.includes(targetUser._id)) {
|
||||
logger.warn(`Host already shared with user: ${targetUsername}`);
|
||||
return callback({ error: 'Already shared' });
|
||||
}
|
||||
|
||||
host.users.push(targetUser._id);
|
||||
await host.save();
|
||||
|
||||
logger.info(`Host shared successfully: ${hostId} -> ${targetUsername}`);
|
||||
callback({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Host sharing error:', error);
|
||||
callback({ error: 'Failed to share host' });
|
||||
}
|
||||
const result = await getHosts(userId);
|
||||
socket.emit(result.error ? "error" : "hostsFound", result);
|
||||
console.log(result.error || `Hosts found`);
|
||||
});
|
||||
|
||||
socket.on("deleteHost", async (data) => {
|
||||
const { userId, hostConfig } = data;
|
||||
if (!userId || !hostConfig) {
|
||||
socket.emit("error", "User ID and host config are required");
|
||||
return;
|
||||
socket.on('deleteUser', async ({ userId, sessionToken }, callback) => {
|
||||
try {
|
||||
logger.debug(`Deleting user: ${userId}`);
|
||||
|
||||
const user = await User.findOne({ _id: userId, sessionToken });
|
||||
if (!user) {
|
||||
logger.warn(`Invalid session for user: ${userId}`);
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
await Host.deleteMany({ createdBy: userId });
|
||||
await User.deleteOne({ _id: userId });
|
||||
|
||||
logger.info(`User deleted: ${userId}`);
|
||||
callback({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('User deletion error:', error);
|
||||
callback({ error: 'Failed to delete user' });
|
||||
}
|
||||
const result = await deleteHost(userId, hostConfig);
|
||||
socket.emit(result.error ? "error" : "hostDeleted", result);
|
||||
console.log(result.error || `Host deleted`);
|
||||
});
|
||||
|
||||
socket.on("editHost", async (data) => {
|
||||
const { userId, oldHostConfig, newHostConfig } = data;
|
||||
if (!userId || !oldHostConfig || !newHostConfig) {
|
||||
socket.emit("error", "User ID, old host config, and new host config are required");
|
||||
return;
|
||||
socket.on("editHost", async ({ userId, sessionToken, oldHostConfig, newHostConfig }, callback) => {
|
||||
try {
|
||||
logger.debug(`Editing host for user: ${userId}`);
|
||||
|
||||
if (!oldHostConfig || !newHostConfig) {
|
||||
logger.warn('Missing host configurations');
|
||||
return callback({ error: 'Missing host configurations' });
|
||||
}
|
||||
|
||||
const user = await User.findOne({ _id: userId, sessionToken });
|
||||
if (!user) {
|
||||
logger.warn(`Invalid session for user: ${userId}`);
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
const hosts = await Host.find({ createdBy: userId });
|
||||
const host = hosts.find(h => {
|
||||
const decryptedConfig = decryptData(h.config, userId, sessionToken);
|
||||
return decryptedConfig && decryptedConfig.ip === oldHostConfig.ip;
|
||||
});
|
||||
|
||||
if (!host) {
|
||||
logger.warn(`Host not found or unauthorized`);
|
||||
return callback({ error: 'Host not found' });
|
||||
}
|
||||
|
||||
const cleanConfig = {
|
||||
ip: newHostConfig.ip.trim(),
|
||||
user: newHostConfig.user.trim(),
|
||||
port: newHostConfig.port || 22,
|
||||
name: newHostConfig.name.trim(),
|
||||
password: newHostConfig.password?.trim() || undefined,
|
||||
rsaKey: newHostConfig.rsaKey?.trim() || undefined
|
||||
};
|
||||
|
||||
const encryptedConfig = encryptData(cleanConfig, userId, sessionToken);
|
||||
if (!encryptedConfig) {
|
||||
logger.error('Encryption failed for host config');
|
||||
return callback({ error: 'Configuration encryption failed' });
|
||||
}
|
||||
|
||||
host.config = encryptedConfig;
|
||||
await host.save();
|
||||
|
||||
logger.info(`Host edited successfully`);
|
||||
callback({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Host edit error:', error);
|
||||
callback({ error: 'Failed to edit host' });
|
||||
}
|
||||
const result = await editHost(userId, oldHostConfig, newHostConfig);
|
||||
socket.emit(result.error ? "error" : "hostEdited", result);
|
||||
console.log(result.error || `Host edited`);
|
||||
});
|
||||
|
||||
socket.on("createFolder", async (data) => {
|
||||
const { userId, folderName } = data;
|
||||
if (!userId || !folderName) {
|
||||
socket.emit("error", "User ID and folder name are required");
|
||||
return;
|
||||
}
|
||||
const result = await createFolder(userId, folderName);
|
||||
socket.emit(result.error ? "error" : "folderCreated", result);
|
||||
console.log(result.error || `Folder created`);
|
||||
});
|
||||
socket.on('verifySession', async ({ sessionToken }, callback) => {
|
||||
try {
|
||||
const user = await User.findOne({ sessionToken });
|
||||
if (!user) {
|
||||
logger.warn(`Invalid session token: ${sessionToken}`);
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
socket.on("moveHostToFolder", async (data) => {
|
||||
const { userId, hostConfig, folderName } = data;
|
||||
if (!userId || !hostConfig || !folderName) {
|
||||
socket.emit("error", "User ID, host config, and folder name are required");
|
||||
return;
|
||||
callback({ success: true, user: {
|
||||
id: user._id,
|
||||
username: user.username
|
||||
}});
|
||||
} catch (error) {
|
||||
logger.error('Session verification error:', error);
|
||||
callback({ error: 'Session verification failed' });
|
||||
}
|
||||
const result = await moveHostToFolder(userId, hostConfig, folderName);
|
||||
socket.emit(result.error ? "error" : "hostMoved", result);
|
||||
console.log(result.error || `Host moved to folder`);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(8082, '0.0.0.0', async () => {
|
||||
console.log("Server is running on port 8082");
|
||||
await connectToMongoDB();
|
||||
server.listen(8082, () => {
|
||||
logger.info('Server running on port 8082');
|
||||
});
|
||||
@@ -4,27 +4,33 @@ const SSHClient = require("ssh2").Client;
|
||||
|
||||
const server = http.createServer();
|
||||
const io = socketIo(server, {
|
||||
path: "/ssh.io/socket.io", // Corrected path for socket.io
|
||||
path: "/ssh.io/socket.io",
|
||||
cors: {
|
||||
origin: "*", // Temporarily set to '*' to allow all origins. Change to specific URLs if needed.
|
||||
origin: "*",
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
},
|
||||
allowEIO3: true
|
||||
});
|
||||
|
||||
const logger = {
|
||||
info: (...args) => console.log(`🔧 [${new Date().toISOString()}] INFO:`, ...args),
|
||||
error: (...args) => console.error(`❌ [${new Date().toISOString()}] ERROR:`, ...args),
|
||||
warn: (...args) => console.warn(`⚠️ [${new Date().toISOString()}] WARN:`, ...args),
|
||||
debug: (...args) => console.debug(`🔍 [${new Date().toISOString()}] DEBUG:`, ...args)
|
||||
};
|
||||
|
||||
io.on("connection", (socket) => {
|
||||
console.log("New socket connection established");
|
||||
logger.info("New socket connection established");
|
||||
|
||||
let stream = null;
|
||||
|
||||
socket.on("connectToHost", (cols, rows, hostConfig) => {
|
||||
if (!hostConfig || !hostConfig.ip || !hostConfig.user || (!hostConfig.password && !hostConfig.rsaKey) || !hostConfig.port) {
|
||||
console.error("Invalid hostConfig received:", hostConfig);
|
||||
logger.error("Invalid hostConfig received:", hostConfig);
|
||||
return;
|
||||
}
|
||||
|
||||
// Redact only sensitive info for logging
|
||||
const safeHostConfig = {
|
||||
ip: hostConfig.ip,
|
||||
port: hostConfig.port,
|
||||
@@ -33,57 +39,52 @@ io.on("connection", (socket) => {
|
||||
rsaKey: hostConfig.rsaKey ? '***REDACTED***' : undefined,
|
||||
};
|
||||
|
||||
console.log("Received hostConfig:", safeHostConfig);
|
||||
logger.info("Received hostConfig:", safeHostConfig);
|
||||
const { ip, port, user, password, rsaKey } = hostConfig;
|
||||
|
||||
const conn = new SSHClient();
|
||||
conn
|
||||
.on("ready", function () {
|
||||
console.log("SSH connection established");
|
||||
logger.info("SSH connection established");
|
||||
|
||||
conn.shell({ term: "xterm-256color" }, function (err, newStream) {
|
||||
if (err) {
|
||||
console.error("Error:", err.message);
|
||||
logger.error("Error:", err.message);
|
||||
socket.emit("error", err.message);
|
||||
return;
|
||||
}
|
||||
stream = newStream;
|
||||
|
||||
// Set initial terminal size
|
||||
stream.setWindow(rows, cols, rows * 100, cols * 100);
|
||||
|
||||
// Pipe SSH output to client
|
||||
stream.on("data", function (data) {
|
||||
socket.emit("data", data);
|
||||
});
|
||||
|
||||
stream.on("close", function () {
|
||||
console.log("SSH stream closed");
|
||||
logger.info("SSH stream closed");
|
||||
conn.end();
|
||||
});
|
||||
|
||||
// Send keystrokes from terminal to SSH
|
||||
socket.on("data", function (data) {
|
||||
stream.write(data);
|
||||
});
|
||||
|
||||
// Resize SSH terminal when client resizes
|
||||
socket.on("resize", ({ cols, rows }) => {
|
||||
if (stream && stream.setWindow) {
|
||||
stream.setWindow(rows, cols, rows * 100, cols * 100);
|
||||
}
|
||||
});
|
||||
|
||||
// Auto-send initial terminal size to backend
|
||||
socket.emit("resize", { cols, rows });
|
||||
});
|
||||
})
|
||||
.on("close", function () {
|
||||
console.log("SSH connection closed");
|
||||
logger.info("SSH connection closed");
|
||||
socket.emit("error", "SSH connection closed");
|
||||
})
|
||||
.on("error", function (err) {
|
||||
console.error("Error:", err.message);
|
||||
logger.error("Error:", err.message);
|
||||
socket.emit("error", err.message);
|
||||
})
|
||||
.connect({
|
||||
@@ -96,10 +97,10 @@ io.on("connection", (socket) => {
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
console.log("Client disconnected");
|
||||
logger.info("Client disconnected");
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(8081, '0.0.0.0', () => {
|
||||
console.log("Server is running on port 8081");
|
||||
logger.info("Server is running on port 8081");
|
||||
});
|
||||
@@ -20,7 +20,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
if (file.name.endsWith('.rsa') || file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.der') || file.name.endsWith('.p8') || file.name.endsWith('.ssh')) {
|
||||
if (file.name.endsWith('.rsa') || file.name.endsWith('.key') || file.name.endsWith('.pem') || file.name.endsWith('.der') || file.name.endsWith('.p8') || file.name.endsWith('.ssh') || file.name.endsWith('.pub')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
setForm({ ...form, rsaKey: event.target.result });
|
||||
@@ -65,7 +65,9 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (isFormValid()) handleAddHost();
|
||||
if (isFormValid()) {
|
||||
handleAddHost();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
|
||||
@@ -176,10 +178,13 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
||||
<FormControl>
|
||||
<FormLabel>Remember Host</FormLabel>
|
||||
<Checkbox
|
||||
checked={form.rememberHost ?? false}
|
||||
checked={form.rememberHost}
|
||||
onChange={(e) => setForm({ ...form, rememberHost: e.target.checked })}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -12,8 +12,7 @@ import {
|
||||
DialogContent,
|
||||
ModalDialog,
|
||||
Select,
|
||||
Option,
|
||||
Checkbox
|
||||
Option
|
||||
} from '@mui/joy';
|
||||
import theme from '/src/theme';
|
||||
|
||||
@@ -41,6 +40,11 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleEditHostInternal = (form) => {
|
||||
const updatedForm = { ...form, name: form.name || form.ip };
|
||||
handleEditHost(updatedForm);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (hostConfig) {
|
||||
setForm({
|
||||
@@ -81,7 +85,7 @@ const EditHostModal = ({ isHidden, form, setForm, handleEditHost, setIsEditHostH
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
if (isFormValid()) handleEditHost();
|
||||
if (isFormValid()) handleEditHostInternal(form);
|
||||
}}
|
||||
>
|
||||
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
|
||||
|
||||
Reference in New Issue
Block a user