Optimized pasting, fixed host naming.
This commit is contained in:
234
package-lock.json
generated
234
package-lock.json
generated
@@ -13,7 +13,7 @@
|
||||
"@fontsource/inter": "^5.1.1",
|
||||
"@mui/icons-material": "^6.4.7",
|
||||
"@mui/joy": "^5.0.0-beta.51",
|
||||
"@tailwindcss/vite": "^4.0.8",
|
||||
"@tailwindcss/vite": "^4.0.15",
|
||||
"@tiptap/extension-link": "^2.11.5",
|
||||
"@tiptap/pm": "^2.11.5",
|
||||
"@tiptap/react": "^2.11.5",
|
||||
@@ -42,7 +42,7 @@
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"ssh2": "^1.16.0",
|
||||
"tailwindcss": "^4.0.8"
|
||||
"tailwindcss": "^4.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
@@ -1226,15 +1226,6 @@
|
||||
"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",
|
||||
@@ -2012,42 +2003,42 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@tailwindcss/node": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.8.tgz",
|
||||
"integrity": "sha512-FKArQpbrbwv08TNT0k7ejYXpF+R8knZFAatNc0acOxbgeqLzwb86r+P3LGOjIeI3Idqe9CVkZrh4GlsJLJKkkw==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.15.tgz",
|
||||
"integrity": "sha512-IODaJjNmiasfZX3IoS+4Em3iu0fD2HS0/tgrnkYfW4hyUor01Smnr5eY3jc4rRgaTDrJlDmBTHbFO0ETTDaxWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"enhanced-resolve": "^5.18.1",
|
||||
"jiti": "^2.4.2",
|
||||
"tailwindcss": "4.0.8"
|
||||
"tailwindcss": "4.0.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.8.tgz",
|
||||
"integrity": "sha512-KfMcuAu/Iw+DcV1e8twrFyr2yN8/ZDC/odIGta4wuuJOGkrkHZbvJvRNIbQNhGh7erZTYV6Ie0IeD6WC9Y8Hcw==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.15.tgz",
|
||||
"integrity": "sha512-e0uHrKfPu7JJGMfjwVNyt5M0u+OP8kUmhACwIRlM+JNBuReDVQ63yAD1NWe5DwJtdaHjugNBil76j+ks3zlk6g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tailwindcss/oxide-android-arm64": "4.0.8",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.0.8",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.0.8",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.0.8",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.8",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.0.8",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.0.8",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.0.8",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.0.8",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.0.8",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.0.8"
|
||||
"@tailwindcss/oxide-android-arm64": "4.0.15",
|
||||
"@tailwindcss/oxide-darwin-arm64": "4.0.15",
|
||||
"@tailwindcss/oxide-darwin-x64": "4.0.15",
|
||||
"@tailwindcss/oxide-freebsd-x64": "4.0.15",
|
||||
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.15",
|
||||
"@tailwindcss/oxide-linux-arm64-gnu": "4.0.15",
|
||||
"@tailwindcss/oxide-linux-arm64-musl": "4.0.15",
|
||||
"@tailwindcss/oxide-linux-x64-gnu": "4.0.15",
|
||||
"@tailwindcss/oxide-linux-x64-musl": "4.0.15",
|
||||
"@tailwindcss/oxide-win32-arm64-msvc": "4.0.15",
|
||||
"@tailwindcss/oxide-win32-x64-msvc": "4.0.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-android-arm64": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.8.tgz",
|
||||
"integrity": "sha512-We7K79+Sm4mwJHk26Yzu/GAj7C7myemm7PeXvpgMxyxO70SSFSL3uCcqFbz9JA5M5UPkrl7N9fkBe/Y0iazqpA==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.15.tgz",
|
||||
"integrity": "sha512-EBuyfSKkom7N+CB3A+7c0m4+qzKuiN0WCvzPvj5ZoRu4NlQadg/mthc1tl5k9b5ffRGsbDvP4k21azU4VwVk3Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2061,9 +2052,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.8.tgz",
|
||||
"integrity": "sha512-Lv9Isi2EwkCTG1sRHNDi0uRNN1UGFdEThUAGFrydRmQZnraGLMjN8gahzg2FFnOizDl7LB2TykLUuiw833DSNg==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.15.tgz",
|
||||
"integrity": "sha512-ObVAnEpLepMhV9VoO0JSit66jiN5C4YCqW3TflsE9boo2Z7FIjV80RFbgeL2opBhtxbaNEDa6D0/hq/EP03kgQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2077,9 +2068,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.8.tgz",
|
||||
"integrity": "sha512-fWfywfYIlSWtKoqWTjukTHLWV3ARaBRjXCC2Eo0l6KVpaqGY4c2y8snUjp1xpxUtpqwMvCvFWFaleMoz1Vhzlw==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.15.tgz",
|
||||
"integrity": "sha512-IElwoFhUinOr9MyKmGTPNi1Rwdh68JReFgYWibPWTGuevkHkLWKEflZc2jtI5lWZ5U9JjUnUfnY43I4fEXrc4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2093,9 +2084,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.8.tgz",
|
||||
"integrity": "sha512-SO+dyvjJV9G94bnmq2288Ke0BIdvrbSbvtPLaQdqjqHR83v5L2fWADyFO+1oecHo9Owsk8MxcXh1agGVPIKIqw==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.15.tgz",
|
||||
"integrity": "sha512-6BLLqyx7SIYRBOnTZ8wgfXANLJV5TQd3PevRJZp0vn42eO58A2LykRKdvL1qyPfdpmEVtF+uVOEZ4QTMqDRAWA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2109,9 +2100,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.8.tgz",
|
||||
"integrity": "sha512-ZSHggWiEblQNV69V0qUK5vuAtHP+I+S2eGrKGJ5lPgwgJeAd6GjLsVBN+Mqn2SPVfYM3BOpS9jX/zVg9RWQVDQ==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.15.tgz",
|
||||
"integrity": "sha512-Zy63EVqO9241Pfg6G0IlRIWyY5vNcWrL5dd2WAKVJZRQVeolXEf1KfjkyeAAlErDj72cnyXObEZjMoPEKHpdNw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2125,9 +2116,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.8.tgz",
|
||||
"integrity": "sha512-xWpr6M0OZLDNsr7+bQz+3X7zcnDJZJ1N9gtBWCtfhkEtDjjxYEp+Lr5L5nc/yXlL4MyCHnn0uonGVXy3fhxaVA==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.15.tgz",
|
||||
"integrity": "sha512-2NemGQeaTbtIp1Z2wyerbVEJZTkAWhMDOhhR5z/zJ75yMNf8yLnE+sAlyf6yGDNr+1RqvWrRhhCFt7i0CIxe4Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2141,9 +2132,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.8.tgz",
|
||||
"integrity": "sha512-5tz2IL7LN58ssGEq7h/staD7pu/izF/KeMWdlJ86WDe2Ah46LF3ET6ZGKTr5eZMrnEA0M9cVFuSPprKRHNgjeg==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.15.tgz",
|
||||
"integrity": "sha512-342GVnhH/6PkVgKtEzvNVuQ4D+Q7B7qplvuH20Cfz9qEtydG6IQczTZ5IT4JPlh931MG1NUCVxg+CIorr1WJyw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2157,9 +2148,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.8.tgz",
|
||||
"integrity": "sha512-KSzMkhyrxAQyY2o194NKVKU9j/c+NFSoMvnHWFaNHKi3P1lb+Vq1UC19tLHrmxSkKapcMMu69D7+G1+FVGNDXQ==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.15.tgz",
|
||||
"integrity": "sha512-g76GxlKH124RuGqacCEFc2nbzRl7bBrlC8qDQMiUABkiifDRHOIUjgKbLNG4RuR9hQAD/MKsqZ7A8L08zsoBrw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2173,9 +2164,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.8.tgz",
|
||||
"integrity": "sha512-yFYKG5UtHTRimjtqxUWXBgI4Tc6NJe3USjRIVdlTczpLRxq/SFwgzGl5JbatCxgSRDPBFwRrNPxq+ukfQFGdrw==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.15.tgz",
|
||||
"integrity": "sha512-Gg/Y1XrKEvKpq6WeNt2h8rMIKOBj/W3mNa5NMvkQgMC7iO0+UNLrYmt6zgZufht66HozNpn+tJMbbkZ5a3LczA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2189,9 +2180,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.8.tgz",
|
||||
"integrity": "sha512-tndGujmCSba85cRCnQzXgpA2jx5gXimyspsUYae5jlPyLRG0RjXbDshFKOheVXU4TLflo7FSG8EHCBJ0EHTKdQ==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.15.tgz",
|
||||
"integrity": "sha512-7QtSSJwYZ7ZK1phVgcNZpuf7c7gaCj8Wb0xjliligT5qCGCp79OV2n3SJummVZdw4fbTNKUOYMO7m1GinppZyA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2205,9 +2196,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.8.tgz",
|
||||
"integrity": "sha512-T77jroAc0p4EHVVgTUiNeFn6Nj3jtD3IeNId2X+0k+N1XxfNipy81BEkYErpKLiOkNhpNFjPee8/ZVas29b2OQ==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.15.tgz",
|
||||
"integrity": "sha512-JQ5H+5MLhOjpgNp6KomouE0ZuKmk3hO5h7/ClMNAQ8gZI2zkli3IH8ZqLbd2DVfXDbdxN2xvooIEeIlkIoSCqw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2221,15 +2212,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/vite": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.8.tgz",
|
||||
"integrity": "sha512-+SAq44yLzYlzyrb7QTcFCdU8Xa7FOA0jp+Xby7fPMUie+MY9HhJysM7Vp+vL8qIp8ceQJfLD+FjgJuJ4lL6nyg==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.15.tgz",
|
||||
"integrity": "sha512-JRexava80NijI8cTcLXNM3nQL5A0ptTHI8oJLLe8z1MpNB6p5J4WCdJJP8RoyHu8/eB1JzEdbpH86eGfbuaezQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tailwindcss/node": "4.0.8",
|
||||
"@tailwindcss/oxide": "4.0.8",
|
||||
"lightningcss": "^1.29.1",
|
||||
"tailwindcss": "4.0.8"
|
||||
"@tailwindcss/node": "4.0.15",
|
||||
"@tailwindcss/oxide": "4.0.15",
|
||||
"lightningcss": "1.29.2",
|
||||
"tailwindcss": "4.0.15"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": "^5.2.0 || ^6"
|
||||
@@ -3933,15 +3924,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"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",
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/doctrine": {
|
||||
@@ -5881,12 +5869,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz",
|
||||
"integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
|
||||
"integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3"
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12.0.0"
|
||||
@@ -5896,22 +5884,22 @@
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"lightningcss-darwin-arm64": "1.29.1",
|
||||
"lightningcss-darwin-x64": "1.29.1",
|
||||
"lightningcss-freebsd-x64": "1.29.1",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.29.1",
|
||||
"lightningcss-linux-arm64-gnu": "1.29.1",
|
||||
"lightningcss-linux-arm64-musl": "1.29.1",
|
||||
"lightningcss-linux-x64-gnu": "1.29.1",
|
||||
"lightningcss-linux-x64-musl": "1.29.1",
|
||||
"lightningcss-win32-arm64-msvc": "1.29.1",
|
||||
"lightningcss-win32-x64-msvc": "1.29.1"
|
||||
"lightningcss-darwin-arm64": "1.29.2",
|
||||
"lightningcss-darwin-x64": "1.29.2",
|
||||
"lightningcss-freebsd-x64": "1.29.2",
|
||||
"lightningcss-linux-arm-gnueabihf": "1.29.2",
|
||||
"lightningcss-linux-arm64-gnu": "1.29.2",
|
||||
"lightningcss-linux-arm64-musl": "1.29.2",
|
||||
"lightningcss-linux-x64-gnu": "1.29.2",
|
||||
"lightningcss-linux-x64-musl": "1.29.2",
|
||||
"lightningcss-win32-arm64-msvc": "1.29.2",
|
||||
"lightningcss-win32-x64-msvc": "1.29.2"
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-arm64": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.1.tgz",
|
||||
"integrity": "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
|
||||
"integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -5929,9 +5917,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-darwin-x64": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.1.tgz",
|
||||
"integrity": "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
|
||||
"integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -5949,9 +5937,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-freebsd-x64": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.1.tgz",
|
||||
"integrity": "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
|
||||
"integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -5969,9 +5957,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm-gnueabihf": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.1.tgz",
|
||||
"integrity": "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
|
||||
"integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -5989,9 +5977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-gnu": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.1.tgz",
|
||||
"integrity": "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
|
||||
"integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -6009,9 +5997,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-arm64-musl": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.1.tgz",
|
||||
"integrity": "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
|
||||
"integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -6029,9 +6017,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-gnu": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.1.tgz",
|
||||
"integrity": "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
|
||||
"integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -6049,9 +6037,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-linux-x64-musl": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.1.tgz",
|
||||
"integrity": "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
|
||||
"integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -6069,9 +6057,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-arm64-msvc": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.1.tgz",
|
||||
"integrity": "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
|
||||
"integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -6089,9 +6077,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lightningcss-win32-x64-msvc": {
|
||||
"version": "1.29.1",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.1.tgz",
|
||||
"integrity": "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==",
|
||||
"version": "1.29.2",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
|
||||
"integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -8225,9 +8213,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.8.tgz",
|
||||
"integrity": "sha512-Me7N5CKR+D2A1xdWA5t5+kjjT7bwnxZOE6/yDI/ixJdJokszsn2n++mdU5yJwrsTpqFX2B9ZNMBJDwcqk9C9lw==",
|
||||
"version": "4.0.15",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.15.tgz",
|
||||
"integrity": "sha512-6ZMg+hHdMJpjpeCCFasX7K+U615U9D+7k5/cDK/iRwl6GptF24+I/AbKgOnXhVKePzrEyIXutLv36n4cRsq3Sg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"@fontsource/inter": "^5.1.1",
|
||||
"@mui/icons-material": "^6.4.7",
|
||||
"@mui/joy": "^5.0.0-beta.51",
|
||||
"@tailwindcss/vite": "^4.0.8",
|
||||
"@tailwindcss/vite": "^4.0.15",
|
||||
"@tiptap/extension-link": "^2.11.5",
|
||||
"@tiptap/pm": "^2.11.5",
|
||||
"@tiptap/react": "^2.11.5",
|
||||
@@ -44,7 +44,7 @@
|
||||
"socket.io": "^4.8.1",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"ssh2": "^1.16.0",
|
||||
"tailwindcss": "^4.0.8"
|
||||
"tailwindcss": "^4.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
|
||||
146
src/App.jsx
146
src/App.jsx
@@ -39,6 +39,12 @@ function App() {
|
||||
authMethod: "Select Auth",
|
||||
rememberHost: true,
|
||||
storePassword: true,
|
||||
connectionType: "ssh",
|
||||
rdpDomain: "",
|
||||
rdpWindowsAuthentication: true,
|
||||
rdpConsole: false,
|
||||
vncScaling: "100%",
|
||||
vncQuality: "High"
|
||||
});
|
||||
const [editHostForm, setEditHostForm] = useState({
|
||||
name: "",
|
||||
@@ -223,34 +229,53 @@ function App() {
|
||||
}, []);
|
||||
|
||||
const handleAddHost = () => {
|
||||
if (addHostForm.ip && addHostForm.user && addHostForm.port) {
|
||||
if (addHostForm.ip && addHostForm.port) {
|
||||
if (addHostForm.connectionType === 'ssh' && !addHostForm.user) {
|
||||
setErrorMessage("Please fill out all required fields (IP, User, Port).");
|
||||
setIsErrorHidden(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!addHostForm.rememberHost) {
|
||||
connectToHost();
|
||||
setIsAddHostHidden(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (addHostForm.authMethod === 'Select Auth') {
|
||||
alert("Please select an authentication method.");
|
||||
return;
|
||||
if (addHostForm.connectionType === 'ssh') {
|
||||
if (addHostForm.authMethod === 'Select Auth') {
|
||||
setErrorMessage("Please select an authentication method.");
|
||||
setIsErrorHidden(false);
|
||||
return;
|
||||
}
|
||||
if (addHostForm.authMethod === 'password' && !addHostForm.password) {
|
||||
setIsNoAuthHidden(false);
|
||||
return;
|
||||
}
|
||||
if (addHostForm.authMethod === 'sshKey' && !addHostForm.sshKey) {
|
||||
setIsNoAuthHidden(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (addHostForm.authMethod === 'password' && !addHostForm.password) {
|
||||
setIsNoAuthHidden(false);
|
||||
return;
|
||||
}
|
||||
if (addHostForm.authMethod === 'sshKey' && !addHostForm.sshKey) {
|
||||
else if (!addHostForm.password) {
|
||||
setIsNoAuthHidden(false);
|
||||
return;
|
||||
}
|
||||
|
||||
connectToHost();
|
||||
if (!addHostForm.storePassword) {
|
||||
addHostForm.password = '';
|
||||
try {
|
||||
connectToHost();
|
||||
if (!addHostForm.storePassword) {
|
||||
addHostForm.password = '';
|
||||
}
|
||||
handleSaveHost();
|
||||
setIsAddHostHidden(true);
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message || "Failed to add host");
|
||||
setIsErrorHidden(false);
|
||||
}
|
||||
handleSaveHost();
|
||||
setIsAddHostHidden(true);
|
||||
} else {
|
||||
alert("Please fill out all required fields (IP, User, Port).");
|
||||
setErrorMessage("Please fill out all required fields.");
|
||||
setIsErrorHidden(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -275,25 +300,42 @@ function App() {
|
||||
setActiveTab(nextId);
|
||||
setNextId(nextId + 1);
|
||||
setIsAddHostHidden(true);
|
||||
setAddHostForm({ name: "", folder: "", ip: "", user: "", password: "", sshKey: "", port: 22, authMethod: "Select Auth", rememberHost: true, storePassword: true });
|
||||
setAddHostForm({ name: "", folder: "", ip: "", user: "", password: "", sshKey: "", port: 22, authMethod: "Select Auth", rememberHost: true, storePassword: true, connectionType: "ssh", rdpDomain: "", rdpWindowsAuthentication: true, rdpConsole: false, vncScaling: "100%", vncQuality: "High" });
|
||||
}
|
||||
|
||||
const handleAuthSubmit = (form) => {
|
||||
const updatedTerminals = terminals.map((terminal) => {
|
||||
if (terminal.id === activeTab) {
|
||||
return {
|
||||
...terminal,
|
||||
hostConfig: {
|
||||
...terminal.hostConfig,
|
||||
password: form.password,
|
||||
sshKey: form.sshKey
|
||||
try {
|
||||
setIsNoAuthHidden(true);
|
||||
|
||||
setTimeout(() => {
|
||||
const updatedTerminals = terminals.map((terminal) => {
|
||||
if (terminal.id === activeTab) {
|
||||
return {
|
||||
...terminal,
|
||||
hostConfig: {
|
||||
...terminal.hostConfig,
|
||||
password: form.authMethod === 'password' ? form.password : undefined,
|
||||
sshKey: form.authMethod === 'sshKey' ? form.sshKey : undefined
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
return terminal;
|
||||
});
|
||||
setTerminals(updatedTerminals);
|
||||
setIsNoAuthHidden(true);
|
||||
return terminal;
|
||||
});
|
||||
|
||||
setTerminals(updatedTerminals);
|
||||
|
||||
setNoAuthenticationForm({
|
||||
authMethod: 'Select Auth',
|
||||
password: '',
|
||||
sshKey: '',
|
||||
keyType: '',
|
||||
});
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
console.error("Authentication error:", error);
|
||||
setErrorMessage("Failed to authenticate: " + (error.message || "Unknown error"));
|
||||
setIsErrorHidden(false);
|
||||
}
|
||||
};
|
||||
|
||||
const connectToHostWithConfig = (hostConfig) => {
|
||||
@@ -327,20 +369,30 @@ function App() {
|
||||
setIsLaunchpadOpen(false);
|
||||
}
|
||||
|
||||
const handleSaveHost = () => {
|
||||
let hostConfig = {
|
||||
name: addHostForm.name || addHostForm.ip,
|
||||
folder: addHostForm.folder,
|
||||
ip: addHostForm.ip,
|
||||
user: addHostForm.user,
|
||||
password: addHostForm.authMethod === 'password' ? addHostForm.password : undefined,
|
||||
sshKey: addHostForm.authMethod === 'sshKey' ? addHostForm.sshKey : undefined,
|
||||
port: String(addHostForm.port),
|
||||
}
|
||||
if (userRef.current) {
|
||||
userRef.current.saveHost({
|
||||
hostConfig,
|
||||
});
|
||||
const handleSaveHost = async () => {
|
||||
try {
|
||||
let hostConfig = {
|
||||
name: addHostForm.name || addHostForm.ip,
|
||||
folder: addHostForm.folder,
|
||||
ip: addHostForm.ip,
|
||||
user: addHostForm.user,
|
||||
password: (addHostForm.authMethod === 'password' || addHostForm.connectionType === 'vnc' || addHostForm.connectionType === 'rdp') ? addHostForm.password : undefined,
|
||||
sshKey: addHostForm.connectionType === 'ssh' && addHostForm.authMethod === 'sshKey' ? addHostForm.sshKey : undefined,
|
||||
port: String(addHostForm.port),
|
||||
connectionType: addHostForm.connectionType,
|
||||
rdpDomain: addHostForm.connectionType === 'rdp' ? addHostForm.rdpDomain : undefined,
|
||||
rdpWindowsAuthentication: addHostForm.connectionType === 'rdp' ? addHostForm.rdpWindowsAuthentication : undefined,
|
||||
rdpConsole: addHostForm.connectionType === 'rdp' ? addHostForm.rdpConsole : undefined,
|
||||
vncScaling: addHostForm.connectionType === 'vnc' ? addHostForm.vncScaling : undefined,
|
||||
vncQuality: addHostForm.connectionType === 'vnc' ? addHostForm.vncQuality : undefined
|
||||
}
|
||||
if (userRef.current) {
|
||||
await userRef.current.saveHost({
|
||||
hostConfig,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,9 +507,11 @@ function App() {
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
setIsEditHostHidden(true);
|
||||
} catch (error) {
|
||||
throw error;
|
||||
} finally {
|
||||
setIsEditing(false);
|
||||
setIsEditHostHidden(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -465,7 +519,7 @@ function App() {
|
||||
updateEditHostForm(oldConfig);
|
||||
} catch (error) {
|
||||
console.error('Edit failed:', error);
|
||||
setErrorMessage(`Edit failed: ${error}`);
|
||||
setErrorMessage(`Edit failed: ${error.message || error}`);
|
||||
setIsErrorHidden(false);
|
||||
setIsEditing(false);
|
||||
}
|
||||
|
||||
@@ -94,20 +94,15 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
});
|
||||
|
||||
socket.on("connect", () => {
|
||||
console.log("Socket connected, attempting SSH connection...");
|
||||
|
||||
fitAddon.current.fit();
|
||||
resizeTerminal();
|
||||
const { cols, rows } = terminalInstance.current;
|
||||
|
||||
// Check for authentication details
|
||||
if (!hostConfig.password?.trim() && !hostConfig.sshKey?.trim()) {
|
||||
console.log("No authentication provided, showing modal");
|
||||
setIsNoAuthHidden(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure we have proper SSH config with both key field names for backward compatibility
|
||||
const sshConfig = {
|
||||
ip: hostConfig.ip,
|
||||
user: hostConfig.user,
|
||||
@@ -121,9 +116,11 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
fitAddon.current.fit();
|
||||
resizeTerminal();
|
||||
terminalInstance.current.focus();
|
||||
if (terminalInstance.current) {
|
||||
fitAddon.current.fit();
|
||||
resizeTerminal();
|
||||
terminalInstance.current.focus();
|
||||
}
|
||||
}, 50);
|
||||
|
||||
socket.on("data", (data) => {
|
||||
@@ -133,124 +130,49 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
|
||||
let isPasting = false;
|
||||
|
||||
terminalInstance.current.onData((data) => {
|
||||
if (socketRef.current && socketRef.current.connected) {
|
||||
socketRef.current.emit("data", data);
|
||||
}
|
||||
});
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.onData((data) => {
|
||||
if (socketRef.current && socketRef.current.connected) {
|
||||
socketRef.current.emit("data", data);
|
||||
}
|
||||
});
|
||||
|
||||
terminalInstance.current.attachCustomKeyEventHandler((event) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "v") {
|
||||
if (isPasting) return false;
|
||||
isPasting = true;
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// Use a multi-layered approach for clipboard access
|
||||
const pasteFromClipboard = async () => {
|
||||
try {
|
||||
// Try modern Clipboard API first
|
||||
if (navigator.clipboard && navigator.clipboard.readText) {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
if (text && socketRef.current?.connected) {
|
||||
const processedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
|
||||
socketRef.current.emit("data", processedText);
|
||||
return true;
|
||||
}
|
||||
} catch (clipboardErr) {
|
||||
console.warn("Clipboard API failed:", clipboardErr);
|
||||
// Continue to fallbacks
|
||||
terminalInstance.current.attachCustomKeyEventHandler((event) => {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === "v") {
|
||||
event.preventDefault();
|
||||
|
||||
navigator.clipboard.readText()
|
||||
.then(text => {
|
||||
if (text && socketRef.current?.connected) {
|
||||
const processedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
|
||||
socketRef.current.emit("data", processedText);
|
||||
}
|
||||
}
|
||||
|
||||
// Try execCommand fallback
|
||||
if (document.queryCommandSupported && document.queryCommandSupported('paste')) {
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('paste');
|
||||
if (successful) {
|
||||
const text = textarea.value;
|
||||
if (text && socketRef.current?.connected) {
|
||||
const processedText = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
|
||||
socketRef.current.emit("data", processedText);
|
||||
document.body.removeChild(textarea);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (execErr) {
|
||||
console.warn("execCommand paste failed:", execErr);
|
||||
})
|
||||
.catch(() => {
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.write("\r\n*** Paste failed: Clipboard access denied. Please check browser permissions. ***\r\n");
|
||||
}
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
});
|
||||
|
||||
// Show permissions warning and instructions
|
||||
terminalInstance.current.write("\r\n*** To paste: Right-click in terminal and select Paste from context menu ***\r\n");
|
||||
return false;
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
isPasting = false;
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
pasteFromClipboard();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
terminalInstance.current.onKey(({ domEvent }) => {
|
||||
if (domEvent.key === "c" && (domEvent.ctrlKey || domEvent.metaKey)) {
|
||||
const selection = terminalInstance.current.getSelection();
|
||||
if (selection) {
|
||||
// Use a try-catch to handle clipboard failures
|
||||
try {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(selection)
|
||||
.catch(err => {
|
||||
console.warn("Clipboard write failed:", err);
|
||||
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
|
||||
// Store selection in a variable as fallback
|
||||
window.termixInternalClipboard = selection;
|
||||
});
|
||||
} else {
|
||||
// Fallback for browsers without clipboard API
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = selection;
|
||||
textarea.style.position = 'fixed';
|
||||
textarea.style.opacity = '0';
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
const successful = document.execCommand('copy');
|
||||
if (!successful) {
|
||||
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
|
||||
window.termixInternalClipboard = selection;
|
||||
terminalInstance.current.onKey(({ domEvent }) => {
|
||||
if (domEvent.key === "c" && (domEvent.ctrlKey || domEvent.metaKey)) {
|
||||
const selection = terminalInstance.current.getSelection();
|
||||
if (selection) {
|
||||
navigator.clipboard.writeText(selection)
|
||||
.catch(() => {
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.write("\r\n*** Copy failed: Clipboard access denied. Please check browser permissions. ***\r\n");
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn("execCommand copy failed:", err);
|
||||
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
|
||||
window.termixInternalClipboard = selection;
|
||||
}
|
||||
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Copy failed:", err);
|
||||
terminalInstance.current.write("\r\n*** Copy failed: Text copied to internal buffer ***\r\n");
|
||||
window.termixInternalClipboard = selection;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
let authModalShown = false;
|
||||
|
||||
@@ -262,18 +184,22 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
});
|
||||
|
||||
socket.on("disconnect", (reason) => {
|
||||
console.log("Socket disconnected:", reason);
|
||||
terminalInstance.current.write(`\r\n*** Socket disconnected: ${reason} ***\r\n`);
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.write(`\r\n*** Socket disconnected: ${reason} ***\r\n`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("reconnect", (attemptNumber) => {
|
||||
console.log("Socket reconnected after", attemptNumber, "attempts");
|
||||
terminalInstance.current.write(`\r\n*** Socket reconnected after ${attemptNumber} attempts ***\r\n`);
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.write(`\r\n*** Socket reconnected after ${attemptNumber} attempts ***\r\n`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("reconnect_error", (error) => {
|
||||
console.error("Socket reconnect error:", error);
|
||||
terminalInstance.current.write(`\r\n*** Socket reconnect error: ${error.message} ***\r\n`);
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.write(`\r\n*** Socket reconnect error: ${error.message} ***\r\n`);
|
||||
}
|
||||
});
|
||||
|
||||
const pingInterval = setInterval(() => {
|
||||
@@ -284,13 +210,11 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
|
||||
socketRef.current.on("pong", () => {});
|
||||
|
||||
// Add right-click context menu for paste
|
||||
const element = terminalInstance.current.element;
|
||||
if (element) {
|
||||
if (terminalInstance.current && terminalInstance.current.element) {
|
||||
const element = terminalInstance.current.element;
|
||||
element.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
// Create and show context menu
|
||||
|
||||
const contextMenu = document.createElement('div');
|
||||
contextMenu.className = 'terminal-context-menu';
|
||||
contextMenu.style.position = 'fixed';
|
||||
@@ -302,8 +226,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
contextMenu.style.padding = '4px 0';
|
||||
contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
|
||||
contextMenu.style.zIndex = '1000';
|
||||
|
||||
// Create copy option
|
||||
|
||||
const copyOption = document.createElement('div');
|
||||
copyOption.innerText = 'Copy';
|
||||
copyOption.className = 'terminal-context-menu-item';
|
||||
@@ -317,29 +240,31 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
copyOption.onmouseout = () => {
|
||||
copyOption.style.backgroundColor = 'transparent';
|
||||
};
|
||||
|
||||
// Handle copy action
|
||||
|
||||
copyOption.onclick = () => {
|
||||
const selection = terminalInstance.current.getSelection();
|
||||
if (selection) {
|
||||
// Try to copy using clipboard API
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(selection)
|
||||
.catch(err => {
|
||||
console.warn("Clipboard write failed:", err);
|
||||
window.termixInternalClipboard = selection;
|
||||
if (terminalInstance.current) {
|
||||
const selection = terminalInstance.current.getSelection();
|
||||
if (selection) {
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(selection)
|
||||
.catch(err => {
|
||||
console.warn("Clipboard write failed:", err);
|
||||
window.termixInternalClipboard = selection;
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.write("\r\n*** Copied to internal clipboard ***\r\n");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
window.termixInternalClipboard = selection;
|
||||
if (terminalInstance.current) {
|
||||
terminalInstance.current.write("\r\n*** Copied to internal clipboard ***\r\n");
|
||||
});
|
||||
} else {
|
||||
// Store in internal clipboard
|
||||
window.termixInternalClipboard = selection;
|
||||
terminalInstance.current.write("\r\n*** Copied to internal clipboard ***\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
document.body.removeChild(contextMenu);
|
||||
};
|
||||
|
||||
// Create paste option
|
||||
|
||||
const pasteOption = document.createElement('div');
|
||||
pasteOption.innerText = 'Paste';
|
||||
pasteOption.className = 'terminal-context-menu-item';
|
||||
@@ -353,11 +278,9 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
pasteOption.onmouseout = () => {
|
||||
pasteOption.style.backgroundColor = 'transparent';
|
||||
};
|
||||
|
||||
// Handle paste action
|
||||
|
||||
pasteOption.onclick = async () => {
|
||||
try {
|
||||
// Try clipboard API first
|
||||
if (navigator.clipboard && navigator.clipboard.readText) {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
@@ -366,32 +289,28 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible, setIsNoAuthHidde
|
||||
socketRef.current.emit("data", processedText);
|
||||
}
|
||||
} catch (err) {
|
||||
// Use fallback or internal clipboard
|
||||
if (window.termixInternalClipboard) {
|
||||
const processedText = window.termixInternalClipboard.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
|
||||
socketRef.current.emit("data", processedText);
|
||||
} else {
|
||||
} else if (terminalInstance.current) {
|
||||
terminalInstance.current.write("\r\n*** Paste failed: No clipboard content available ***\r\n");
|
||||
}
|
||||
}
|
||||
} else if (window.termixInternalClipboard) {
|
||||
// Use internal clipboard if available
|
||||
const processedText = window.termixInternalClipboard.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r");
|
||||
socketRef.current.emit("data", processedText);
|
||||
} else {
|
||||
} else if (terminalInstance.current) {
|
||||
terminalInstance.current.write("\r\n*** Paste failed: No clipboard content available ***\r\n");
|
||||
}
|
||||
} finally {
|
||||
document.body.removeChild(contextMenu);
|
||||
}
|
||||
};
|
||||
|
||||
// Add options to menu
|
||||
|
||||
contextMenu.appendChild(copyOption);
|
||||
contextMenu.appendChild(pasteOption);
|
||||
document.body.appendChild(contextMenu);
|
||||
|
||||
// Remove menu when clicking elsewhere
|
||||
|
||||
const removeMenu = (e) => {
|
||||
if (!contextMenu.contains(e.target)) {
|
||||
document.body.removeChild(contextMenu);
|
||||
|
||||
@@ -149,6 +149,27 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
|
||||
if (!currentUser.current) return onFailure("Not authenticated");
|
||||
|
||||
try {
|
||||
const existingHosts = await getAllHosts();
|
||||
|
||||
const duplicateNameHost = existingHosts.find(host =>
|
||||
host.config.name &&
|
||||
host.config.name.toLowerCase() === hostConfig.hostConfig.name.toLowerCase()
|
||||
);
|
||||
|
||||
if (duplicateNameHost) {
|
||||
return onFailure("A host with this name already exists. Please choose a different name.");
|
||||
}
|
||||
|
||||
if (!hostConfig.hostConfig.name) {
|
||||
const duplicateIpHost = existingHosts.find(host =>
|
||||
host.config.ip.toLowerCase() === hostConfig.hostConfig.ip.toLowerCase()
|
||||
);
|
||||
|
||||
if (duplicateIpHost) {
|
||||
return onFailure("A host with this IP already exists. Please provide a unique name.");
|
||||
}
|
||||
}
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("saveHostConfig", {
|
||||
userId: currentUser.current.id,
|
||||
@@ -222,6 +243,18 @@ export const User = forwardRef(({ onLoginSuccess, onCreateSuccess, onDeleteSucce
|
||||
if (!currentUser.current) return onFailure("Not authenticated");
|
||||
|
||||
try {
|
||||
const existingHosts = await getAllHosts();
|
||||
|
||||
const duplicateNameHost = existingHosts.find(host =>
|
||||
host.config.name &&
|
||||
host.config.name.toLowerCase() === newHostConfig.name.toLowerCase() &&
|
||||
host.config.ip.toLowerCase() !== oldHostConfig.ip.toLowerCase()
|
||||
);
|
||||
|
||||
if (duplicateNameHost) {
|
||||
return onFailure("A host with this name already exists. Please choose a different name.");
|
||||
}
|
||||
|
||||
const response = await new Promise((resolve) => {
|
||||
socketRef.current.emit("editHost", {
|
||||
userId: currentUser.current.id,
|
||||
|
||||
@@ -190,14 +190,31 @@ io.of('/database.io').on('connection', (socket) => {
|
||||
|
||||
const finalName = cleanConfig.name || cleanConfig.ip;
|
||||
|
||||
const existingHost = await Host.findOne({
|
||||
name: finalName,
|
||||
createdBy: userId
|
||||
// Check for hosts with the same name (case insensitive)
|
||||
const existingHostByName = await Host.findOne({
|
||||
createdBy: userId,
|
||||
name: { $regex: new RegExp('^' + finalName + '$', 'i') }
|
||||
});
|
||||
|
||||
if (existingHost) {
|
||||
if (existingHostByName) {
|
||||
logger.warn(`Host with name ${finalName} already exists for user: ${userId}`);
|
||||
return callback({ error: 'Host with this name already exists' });
|
||||
return callback({ error: `Host with name "${finalName}" already exists. Please choose a different name.` });
|
||||
}
|
||||
|
||||
// Prevent duplicate IPs if using IP as name
|
||||
if (!cleanConfig.name) {
|
||||
const existingHostByIp = await Host.findOne({
|
||||
createdBy: userId,
|
||||
config: { $regex: new RegExp(cleanConfig.ip, 'i') }
|
||||
});
|
||||
|
||||
if (existingHostByIp) {
|
||||
const decryptedConfig = decryptData(existingHostByIp.config, userId, sessionToken);
|
||||
if (decryptedConfig && decryptedConfig.ip.toLowerCase() === cleanConfig.ip.toLowerCase()) {
|
||||
logger.warn(`Host with IP ${cleanConfig.ip} already exists for user: ${userId}`);
|
||||
return callback({ error: `Host with IP "${cleanConfig.ip}" already exists. Please provide a unique name.` });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const encryptedConfig = encryptData(cleanConfig, userId, sessionToken);
|
||||
@@ -397,6 +414,7 @@ io.of('/database.io').on('connection', (socket) => {
|
||||
return callback({ error: 'Invalid session' });
|
||||
}
|
||||
|
||||
// Find the host to be edited
|
||||
const hosts = await Host.find({ createdBy: userId });
|
||||
const host = hosts.find(h => {
|
||||
const decryptedConfig = decryptData(h.config, userId, sessionToken);
|
||||
@@ -408,6 +426,37 @@ io.of('/database.io').on('connection', (socket) => {
|
||||
return callback({ error: 'Host not found' });
|
||||
}
|
||||
|
||||
const finalName = newHostConfig.name?.trim() || newHostConfig.ip.trim();
|
||||
|
||||
// If the name is being changed, check for duplicates using case-insensitive comparison
|
||||
if (finalName.toLowerCase() !== host.name.toLowerCase()) {
|
||||
// Check for duplicate name using regex for case-insensitive comparison
|
||||
const duplicateNameHost = await Host.findOne({
|
||||
createdBy: userId,
|
||||
_id: { $ne: host._id }, // Exclude the current host
|
||||
name: { $regex: new RegExp('^' + finalName + '$', 'i') }
|
||||
});
|
||||
|
||||
if (duplicateNameHost) {
|
||||
logger.warn(`Host with name ${finalName} already exists for user: ${userId}`);
|
||||
return callback({ error: `Host with name "${finalName}" already exists. Please choose a different name.` });
|
||||
}
|
||||
}
|
||||
|
||||
// If IP is changed and no custom name provided, check for duplicate IP
|
||||
if (newHostConfig.ip !== oldHostConfig.ip && !newHostConfig.name) {
|
||||
const duplicateIpHost = hosts.find(h => {
|
||||
if (h._id.toString() === host._id.toString()) return false;
|
||||
const decryptedConfig = decryptData(h.config, userId, sessionToken);
|
||||
return decryptedConfig && decryptedConfig.ip.toLowerCase() === newHostConfig.ip.toLowerCase();
|
||||
});
|
||||
|
||||
if (duplicateIpHost) {
|
||||
logger.warn(`Host with IP ${newHostConfig.ip} already exists for user: ${userId}`);
|
||||
return callback({ error: `Host with IP "${newHostConfig.ip}" already exists. Please provide a unique name.` });
|
||||
}
|
||||
}
|
||||
|
||||
const cleanConfig = {
|
||||
name: newHostConfig.name?.trim(),
|
||||
folder: newHostConfig.folder?.trim() || null,
|
||||
@@ -424,6 +473,7 @@ io.of('/database.io').on('connection', (socket) => {
|
||||
return callback({ error: 'Configuration encryption failed' });
|
||||
}
|
||||
|
||||
host.name = finalName;
|
||||
host.config = encryptedConfig;
|
||||
host.folder = cleanConfig.folder;
|
||||
await host.save();
|
||||
@@ -432,7 +482,7 @@ io.of('/database.io').on('connection', (socket) => {
|
||||
callback({ success: true });
|
||||
} catch (error) {
|
||||
logger.error('Host edit error:', error);
|
||||
callback({ error: 'Failed to edit host' });
|
||||
callback({ error: `Failed to edit host: ${error.message}` });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -25,6 +25,8 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [showError, setShowError] = useState(false);
|
||||
|
||||
const handleFileChange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
@@ -102,12 +104,30 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
if (!form.ip?.trim() || !form.user?.trim() || !form.port) {
|
||||
alert("Please fill out all required fields (IP, User, Port).");
|
||||
|
||||
setErrorMessage("");
|
||||
setShowError(false);
|
||||
|
||||
if (!form.ip?.trim()) {
|
||||
setErrorMessage("Please provide an IP address.");
|
||||
setShowError(true);
|
||||
return;
|
||||
}
|
||||
handleAddHost();
|
||||
setActiveTab(0);
|
||||
|
||||
if (form.connectionType === 'ssh' && !form.user?.trim()) {
|
||||
setErrorMessage("Please provide a username for SSH connection.");
|
||||
setShowError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
handleAddHost();
|
||||
setActiveTab(0);
|
||||
} catch (error) {
|
||||
console.error("Add host error:", error);
|
||||
setErrorMessage(error.message || "Failed to add host. The host name or IP may already exist.");
|
||||
setShowError(true);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -138,6 +158,18 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
|
||||
mx: 2,
|
||||
}}
|
||||
>
|
||||
{showError && (
|
||||
<div style={{
|
||||
backgroundColor: "#c53030",
|
||||
color: "white",
|
||||
padding: "10px",
|
||||
textAlign: "center",
|
||||
borderTopLeftRadius: "10px",
|
||||
borderTopRightRadius: "10px"
|
||||
}}>
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(e, val) => setActiveTab(val)}
|
||||
|
||||
@@ -24,21 +24,23 @@ import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
||||
|
||||
const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHost }) => {
|
||||
const [form, setForm] = useState({
|
||||
name: hostConfig?.name || '',
|
||||
folder: hostConfig?.folder || '',
|
||||
ip: hostConfig?.ip || '',
|
||||
user: hostConfig?.user || '',
|
||||
port: hostConfig?.port || '',
|
||||
name: '',
|
||||
folder: '',
|
||||
ip: '',
|
||||
user: '',
|
||||
port: '',
|
||||
password: '',
|
||||
sshKey: hostConfig?.sshKey || '',
|
||||
keyType: hostConfig?.keyType || '',
|
||||
authMethod: hostConfig?.authMethod || 'Select Auth',
|
||||
sshKey: '',
|
||||
keyType: '',
|
||||
authMethod: 'Select Auth',
|
||||
storePassword: true,
|
||||
rememberHost: true
|
||||
});
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const [showError, setShowError] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isHidden && hostConfig) {
|
||||
@@ -106,17 +108,10 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
const handleAuthChange = (newMethod) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
authMethod: newMethod
|
||||
}));
|
||||
};
|
||||
|
||||
const handleStorePasswordChange = (checked) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
storePassword: Boolean(checked),
|
||||
password: checked ? prev.password : "",
|
||||
sshKey: checked ? prev.sshKey : "",
|
||||
authMethod: checked ? prev.authMethod : "Select Auth"
|
||||
authMethod: newMethod,
|
||||
password: "",
|
||||
sshKey: "",
|
||||
keyType: "",
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -131,7 +126,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
if (form.storePassword) {
|
||||
if (authMethod === 'Select Auth') return false;
|
||||
if (authMethod === 'password' && !password?.trim()) return false;
|
||||
if (authMethod === 'sshKey' && !sshKey?.trim()) return false;
|
||||
if (authMethod === 'key' && !sshKey?.trim()) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -143,6 +138,23 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
setErrorMessage("");
|
||||
setShowError(false);
|
||||
|
||||
if (!form.ip || !form.user) {
|
||||
setErrorMessage("IP and Username are required fields");
|
||||
setShowError(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.port) {
|
||||
setErrorMessage("Port is required");
|
||||
setShowError(true);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const newConfig = {
|
||||
name: form.name || form.ip,
|
||||
folder: form.folder,
|
||||
@@ -161,6 +173,11 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
}
|
||||
|
||||
await handleEditHost(hostConfig, newConfig);
|
||||
setActiveTab(0);
|
||||
} catch (error) {
|
||||
console.error("Edit host error:", error);
|
||||
setErrorMessage(error.message || "Failed to edit host. The host name may already exist.");
|
||||
setShowError(true);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -196,10 +213,22 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
mx: 2,
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
{showError && (
|
||||
<div style={{
|
||||
backgroundColor: "#c53030",
|
||||
color: "white",
|
||||
padding: "10px",
|
||||
textAlign: "center",
|
||||
borderTopLeftRadius: "10px",
|
||||
borderTopRightRadius: "10px"
|
||||
}}>
|
||||
{errorMessage}
|
||||
</div>
|
||||
)}
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onChange={(e, val) => setActiveTab(val)}
|
||||
sx={{
|
||||
sx={{
|
||||
width: '100%',
|
||||
mb: 0,
|
||||
backgroundColor: theme.palette.general.tertiary,
|
||||
@@ -241,22 +270,21 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
<FormLabel>Host Name</FormLabel>
|
||||
<Input
|
||||
value={form.name}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, name: e.target.value }))}
|
||||
onChange={(e) => setForm({ ...form, name: e.target.value })}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.general.primary,
|
||||
color: theme.palette.text.primary
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Folder</FormLabel>
|
||||
<Input
|
||||
value={form.folder}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, folder: e.target.value }))}
|
||||
value={form.folder || ''}
|
||||
onChange={(e) => setForm({ ...form, folder: e.target.value })}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.general.primary,
|
||||
color: theme.palette.text.primary
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -269,35 +297,38 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
<FormLabel>Host IP</FormLabel>
|
||||
<Input
|
||||
value={form.ip}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, ip: e.target.value }))}
|
||||
onChange={(e) => setForm({ ...form, ip: e.target.value })}
|
||||
required
|
||||
sx={{
|
||||
backgroundColor: theme.palette.general.primary,
|
||||
color: theme.palette.text.primary
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl error={!form.user}>
|
||||
<FormLabel>Host User</FormLabel>
|
||||
<Input
|
||||
value={form.user}
|
||||
onChange={(e) => setForm({ ...form, user: e.target.value })}
|
||||
required
|
||||
sx={{
|
||||
backgroundColor: theme.palette.general.primary,
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl error={form.port < 1 || form.port > 65535}>
|
||||
<FormLabel>Host Port</FormLabel>
|
||||
<Input
|
||||
type="number"
|
||||
value={form.port}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, port: e.target.value }))}
|
||||
onChange={(e) => setForm({ ...form, port: e.target.value })}
|
||||
min={1}
|
||||
max={65535}
|
||||
required
|
||||
sx={{
|
||||
backgroundColor: theme.palette.general.primary,
|
||||
color: theme.palette.text.primary
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl error={!form.user}>
|
||||
<FormLabel>Host User</FormLabel>
|
||||
<Input
|
||||
value={form.user}
|
||||
onChange={(e) => setForm((prev) => ({ ...prev, user: e.target.value }))}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.general.primary,
|
||||
color: theme.palette.text.primary
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -306,23 +337,9 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
|
||||
<TabPanel value={2}>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<FormLabel>Store Password</FormLabel>
|
||||
<Checkbox
|
||||
checked={Boolean(form.storePassword)}
|
||||
onChange={(e) => handleStorePasswordChange(e.target.checked)}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
{form.storePassword && (
|
||||
<>
|
||||
<FormControl error={form.storePassword && (!form.authMethod || form.authMethod === 'Select Auth')}>
|
||||
<FormControl error={!form.authMethod || form.authMethod === 'Select Auth'}>
|
||||
<FormLabel>Authentication Method</FormLabel>
|
||||
<Select
|
||||
value={form.authMethod}
|
||||
@@ -339,13 +356,13 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
</FormControl>
|
||||
|
||||
{form.authMethod === 'password' && (
|
||||
<FormControl error={form.storePassword && !form.password}>
|
||||
<FormControl error={!form.password}>
|
||||
<FormLabel>Password</FormLabel>
|
||||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||
<Input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={form.password}
|
||||
onChange={(e) => setForm(prev => ({ ...prev, password: e.target.value }))}
|
||||
onChange={(e) => setForm({ ...form, password: e.target.value })}
|
||||
sx={{
|
||||
backgroundColor: theme.palette.general.primary,
|
||||
color: theme.palette.text.primary,
|
||||
@@ -367,7 +384,7 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
|
||||
{form.authMethod === 'key' && (
|
||||
<Stack spacing={2}>
|
||||
<FormControl error={form.storePassword && !form.sshKey}>
|
||||
<FormControl error={!form.sshKey}>
|
||||
<FormLabel>SSH Key</FormLabel>
|
||||
<Button
|
||||
component="label"
|
||||
@@ -409,6 +426,26 @@ const EditHostModal = ({ isHidden, hostConfig, setIsEditHostHidden, handleEditHo
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormControl>
|
||||
<FormLabel>Store Password</FormLabel>
|
||||
<Checkbox
|
||||
checked={Boolean(form.storePassword)}
|
||||
onChange={(e) => setForm({
|
||||
...form,
|
||||
storePassword: e.target.checked,
|
||||
password: e.target.checked ? form.password : "",
|
||||
sshKey: e.target.checked ? form.sshKey : "",
|
||||
authMethod: e.target.checked ? form.authMethod : "Select Auth"
|
||||
})}
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
'&.Mui-checked': {
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</TabPanel>
|
||||
</div>
|
||||
|
||||
@@ -43,15 +43,29 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
if(isFormValid()) {
|
||||
handleAuthSubmit(form);
|
||||
setForm (prev => ({
|
||||
...prev,
|
||||
authMethod: 'Select Auth',
|
||||
password: '',
|
||||
sshKey: '',
|
||||
keyType: '',
|
||||
}))
|
||||
e.stopPropagation();
|
||||
|
||||
try {
|
||||
if(isFormValid()) {
|
||||
const formData = {
|
||||
authMethod: form.authMethod,
|
||||
password: form.authMethod === 'password' ? form.password : '',
|
||||
sshKey: form.authMethod === 'sshKey' ? form.sshKey : '',
|
||||
keyType: form.authMethod === 'sshKey' ? form.keyType : '',
|
||||
};
|
||||
|
||||
handleAuthSubmit(formData);
|
||||
|
||||
setForm(prev => ({
|
||||
...prev,
|
||||
authMethod: 'Select Auth',
|
||||
password: '',
|
||||
sshKey: '',
|
||||
keyType: '',
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Authentication form error:", error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,8 +90,7 @@ const NoAuthenticationModal = ({ isHidden, form, setForm, setIsNoAuthHidden, han
|
||||
reader.onload = (event) => {
|
||||
const keyContent = event.target.result;
|
||||
let keyType = 'UNKNOWN';
|
||||
|
||||
// Detect key type from content
|
||||
|
||||
if (keyContent.includes('BEGIN RSA PRIVATE KEY') || keyContent.includes('BEGIN RSA PUBLIC KEY')) {
|
||||
keyType = 'RSA';
|
||||
} else if (keyContent.includes('BEGIN OPENSSH PRIVATE KEY') && keyContent.includes('ssh-ed25519')) {
|
||||
|
||||
Reference in New Issue
Block a user