PRIMEIRO ENVIO
This commit is contained in:
Generated
+961
@@ -0,0 +1,961 @@
|
||||
{
|
||||
"name": "cloneweb-interface",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cloneweb-interface",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "~2.1.34",
|
||||
"negotiator": "0.6.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.4",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
|
||||
"integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"content-type": "~1.0.5",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "~1.2.0",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"on-finished": "~2.4.1",
|
||||
"qs": "~6.14.0",
|
||||
"raw-body": "~2.5.3",
|
||||
"type-is": "~1.6.18",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bound": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
|
||||
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"get-intrinsic": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
"integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.2.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/content-type": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
|
||||
"integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/cookie-signature": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/destroy": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
|
||||
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8",
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
|
||||
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/express": {
|
||||
"version": "4.22.1",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
|
||||
"integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "~1.20.3",
|
||||
"content-disposition": "~0.5.4",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "~0.7.1",
|
||||
"cookie-signature": "~1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "~1.3.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.0",
|
||||
"merge-descriptors": "1.0.3",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"path-to-regexp": "~0.1.12",
|
||||
"proxy-addr": "~2.0.7",
|
||||
"qs": "~6.14.0",
|
||||
"range-parser": "~1.2.1",
|
||||
"safe-buffer": "5.2.1",
|
||||
"send": "~0.19.0",
|
||||
"serve-static": "~1.16.2",
|
||||
"setprototypeof": "1.2.0",
|
||||
"statuses": "~2.0.1",
|
||||
"type-is": "~1.6.18",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/finalhandler": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
|
||||
"integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.4.1",
|
||||
"parseurl": "~1.3.3",
|
||||
"statuses": "~2.0.2",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/http-errors": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
|
||||
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"depd": "~2.0.0",
|
||||
"inherits": "~2.0.4",
|
||||
"setprototypeof": "~1.2.0",
|
||||
"statuses": "~2.0.2",
|
||||
"toidentifier": "~1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-descriptors": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
|
||||
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
|
||||
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/negotiator": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
"integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/on-finished": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
"integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"forwarded": "0.2.0",
|
||||
"ipaddr.js": "1.9.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
|
||||
"integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/range-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-body": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
|
||||
"integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bytes": "~3.1.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"iconv-lite": "~0.4.24",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/send": {
|
||||
"version": "0.19.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
|
||||
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "2.0.0",
|
||||
"destroy": "1.2.0",
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "~0.5.2",
|
||||
"http-errors": "~2.0.1",
|
||||
"mime": "1.6.0",
|
||||
"ms": "2.1.3",
|
||||
"on-finished": "~2.4.1",
|
||||
"range-parser": "~1.2.1",
|
||||
"statuses": "~2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/send/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/serve-static": {
|
||||
"version": "1.16.3",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
|
||||
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"encodeurl": "~2.0.0",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.3",
|
||||
"send": "~0.19.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
"integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-list": "^1.0.0",
|
||||
"side-channel-map": "^1.0.1",
|
||||
"side-channel-weakmap": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-list": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
|
||||
"integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-map": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
|
||||
"integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel-weakmap": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
|
||||
"integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bound": "^1.0.2",
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.5",
|
||||
"object-inspect": "^1.13.3",
|
||||
"side-channel-map": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
|
||||
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/toidentifier": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/type-is": {
|
||||
"version": "1.6.18",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||
"integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "cloneweb-interface",
|
||||
"version": "1.0.0",
|
||||
"description": "Web interface for CloneWeb crawler platform",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "node server.js",
|
||||
"build": "echo 'Build complete'"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"axios": "^1.6.2",
|
||||
"cors": "^2.8.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
// Global state
|
||||
let currentPage = 'dashboard';
|
||||
let cloneHistory = [];
|
||||
let settings = {
|
||||
outputDir: 'M:\\CLONEWEB\\cloned-sites',
|
||||
timeout: 30,
|
||||
retries: 3,
|
||||
excludedDomains: 'facebook.com\ntwitter.com\ninstagram.com\ngoogle-analytics.com\ngoogletagmanager.com'
|
||||
};
|
||||
|
||||
// Initialize app
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
initializeApp();
|
||||
});
|
||||
|
||||
function initializeApp() {
|
||||
setupNavigation();
|
||||
setupThemeToggle();
|
||||
setupForms();
|
||||
checkServiceStatus();
|
||||
loadDashboard();
|
||||
loadHistory();
|
||||
loadSettings();
|
||||
|
||||
// Auto-refresh status every 30 seconds
|
||||
setInterval(checkServiceStatus, 30000);
|
||||
}
|
||||
|
||||
// Navigation
|
||||
function setupNavigation() {
|
||||
const navItems = document.querySelectorAll('.nav-item');
|
||||
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const page = item.getAttribute('data-page');
|
||||
navigateToPage(page);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function navigateToPage(page) {
|
||||
// Update nav items
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.classList.remove('active');
|
||||
if (item.getAttribute('data-page') === page) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Update pages
|
||||
document.querySelectorAll('.page').forEach(p => {
|
||||
p.classList.remove('active');
|
||||
});
|
||||
document.getElementById(page).classList.add('active');
|
||||
|
||||
// Update page title
|
||||
const titles = {
|
||||
'dashboard': 'Dashboard',
|
||||
'new-clone': 'Nova Clonagem',
|
||||
'history': 'Histórico',
|
||||
'settings': 'Configurações'
|
||||
};
|
||||
document.getElementById('pageTitle').textContent = titles[page] || page;
|
||||
|
||||
currentPage = page;
|
||||
|
||||
// Refresh data when navigating
|
||||
if (page === 'dashboard') {
|
||||
loadDashboard();
|
||||
} else if (page === 'history') {
|
||||
loadHistory();
|
||||
}
|
||||
}
|
||||
|
||||
// Theme Toggle
|
||||
function setupThemeToggle() {
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
|
||||
// Load saved theme
|
||||
const savedTheme = localStorage.getItem('theme') || 'light';
|
||||
if (savedTheme === 'dark') {
|
||||
document.body.classList.add('dark-theme');
|
||||
themeToggle.checked = true;
|
||||
}
|
||||
|
||||
themeToggle.addEventListener('change', () => {
|
||||
if (themeToggle.checked) {
|
||||
document.body.classList.add('dark-theme');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark-theme');
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Service Status - verifica o serviço WGET na porta 3002
|
||||
async function checkServiceStatus() {
|
||||
const statusIndicator = document.getElementById('serviceStatus');
|
||||
|
||||
try {
|
||||
// Verificar o serviço WGET na porta 3002 (não 3001)
|
||||
const response = await fetch('http://localhost:3002/health', {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
statusIndicator.className = 'status-indicator online';
|
||||
statusIndicator.querySelector('.status-text').textContent = 'Serviço Online';
|
||||
} else {
|
||||
statusIndicator.className = 'status-indicator offline';
|
||||
statusIndicator.querySelector('.status-text').textContent = 'Serviço Offline';
|
||||
}
|
||||
} catch (error) {
|
||||
statusIndicator.className = 'status-indicator offline';
|
||||
statusIndicator.querySelector('.status-text').textContent = 'Serviço Offline';
|
||||
}
|
||||
}
|
||||
|
||||
// Dashboard
|
||||
function loadDashboard() {
|
||||
// Load from localStorage
|
||||
const history = JSON.parse(localStorage.getItem('cloneHistory') || '[]');
|
||||
cloneHistory = history;
|
||||
|
||||
// Calculate stats
|
||||
const totalSites = history.length;
|
||||
const totalSize = history.reduce((sum, item) => sum + (item.size || 0), 0);
|
||||
const lastClone = history.length > 0 ? history[0].timestamp : null;
|
||||
const successCount = history.filter(item => item.success).length;
|
||||
const successRate = totalSites > 0 ? Math.round((successCount / totalSites) * 100) : 100;
|
||||
|
||||
// Update stats
|
||||
document.getElementById('totalSites').textContent = totalSites;
|
||||
document.getElementById('totalSize').textContent = formatBytes(totalSize);
|
||||
document.getElementById('lastClone').textContent = lastClone ? formatRelativeTime(lastClone) : 'Nunca';
|
||||
document.getElementById('successRate').textContent = successRate + '%';
|
||||
|
||||
// Update recent clones list
|
||||
const recentClonesList = document.getElementById('recentClonesList');
|
||||
|
||||
if (history.length === 0) {
|
||||
recentClonesList.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-inbox"></i>
|
||||
<h3>Nenhuma clonagem ainda</h3>
|
||||
<p>Comece clonando seu primeiro site!</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const recentClones = history.slice(0, 5);
|
||||
recentClonesList.innerHTML = recentClones.map(clone => `
|
||||
<div class="clone-item">
|
||||
<div class="clone-info">
|
||||
<div class="clone-icon">
|
||||
<i class="fas fa-${clone.success ? 'check' : 'times'}"></i>
|
||||
</div>
|
||||
<div class="clone-details">
|
||||
<h4>${clone.url}</h4>
|
||||
<p>${formatRelativeTime(clone.timestamp)} • ${formatBytes(clone.size || 0)} • ${clone.files || 0} arquivos</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clone-actions">
|
||||
${clone.success ? `
|
||||
<button class="btn btn-secondary btn-sm" onclick="openClonedSite('${clone.directory}')">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="openCloneFolder('${clone.directory}')">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function refreshDashboard() {
|
||||
loadDashboard();
|
||||
showToast('Dashboard atualizado!', 'success');
|
||||
}
|
||||
|
||||
// Clone Form
|
||||
function setupForms() {
|
||||
// Clone form
|
||||
const cloneForm = document.getElementById('cloneForm');
|
||||
cloneForm.addEventListener('submit', handleCloneSubmit);
|
||||
|
||||
// Depth slider
|
||||
const depthSlider = document.getElementById('depthLevel');
|
||||
const depthValue = document.getElementById('depthValue');
|
||||
|
||||
depthSlider.addEventListener('input', () => {
|
||||
depthValue.textContent = depthSlider.value + ' níveis';
|
||||
});
|
||||
}
|
||||
|
||||
async function handleCloneSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const url = document.getElementById('cloneUrl').value.trim();
|
||||
const depth = parseInt(document.getElementById('depthLevel').value);
|
||||
const includeImages = document.getElementById('includeImages').checked;
|
||||
const includeCSS = document.getElementById('includeCSS').checked;
|
||||
const includeJS = document.getElementById('includeJS').checked;
|
||||
const convertLinks = document.getElementById('convertLinks').checked;
|
||||
const cloneMethod = document.getElementById('cloneMethod').value;
|
||||
|
||||
if (!url) {
|
||||
showToast('Por favor, insira uma URL válida', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL
|
||||
try {
|
||||
new URL(url);
|
||||
} catch {
|
||||
showToast('URL inválida. Use o formato: https://exemplo.com', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
await startCloning(url, {
|
||||
depth,
|
||||
includeImages,
|
||||
includeCSS,
|
||||
includeJS,
|
||||
convertLinks,
|
||||
method: cloneMethod
|
||||
});
|
||||
}
|
||||
|
||||
async function startCloning(url, options) {
|
||||
const progressCard = document.getElementById('progressCard');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressMessage = document.getElementById('progressMessage');
|
||||
const filesDownloaded = document.getElementById('filesDownloaded');
|
||||
const sizeDownloaded = document.getElementById('sizeDownloaded');
|
||||
const timeElapsed = document.getElementById('timeElapsed');
|
||||
|
||||
// Show progress card
|
||||
progressCard.style.display = 'block';
|
||||
progressBar.style.width = '0%';
|
||||
|
||||
const startTime = Date.now();
|
||||
let progress = 0;
|
||||
|
||||
// Simulate progress
|
||||
const progressInterval = setInterval(() => {
|
||||
progress = Math.min(progress + Math.random() * 10, 90);
|
||||
progressBar.style.width = progress + '%';
|
||||
|
||||
const elapsed = Math.floor((Date.now() - startTime) / 1000);
|
||||
timeElapsed.textContent = elapsed + 's';
|
||||
}, 1000);
|
||||
|
||||
const messages = options.method === 'smart' ? [
|
||||
'🚀 Iniciando Smart Clone (Puppeteer)...',
|
||||
'⏳ Renderizando página completa...',
|
||||
'🖱️ Simulando interação do usuário...',
|
||||
'📜 Carregando conteúdo sob demanda...',
|
||||
'📦 Capturando recursos da rede...',
|
||||
'🔄 Reescrevendo links dinâmicos...',
|
||||
'✨ Otimizando clone final...'
|
||||
] : [
|
||||
'🚀 Iniciando clonagem com WGET...',
|
||||
'⏳ Sem limite de tempo - pode demorar vários minutos...',
|
||||
'🔍 Analisando estrutura do site...',
|
||||
'📄 Baixando páginas HTML...',
|
||||
'🎨 Baixando CSS e estilos...',
|
||||
'⚡ Baixando JavaScript...',
|
||||
'📦 Baixando imagens e assets...',
|
||||
'🔗 Convertendo links para locais...',
|
||||
'💪 Clonagem profissional em andamento...',
|
||||
'✨ Finalizando clonagem...'
|
||||
];
|
||||
|
||||
let messageIndex = 0;
|
||||
progressMessage.textContent = messages[0];
|
||||
|
||||
const messageInterval = setInterval(() => {
|
||||
messageIndex = (messageIndex + 1) % messages.length;
|
||||
progressMessage.textContent = messages[messageIndex];
|
||||
}, 4000);
|
||||
|
||||
try {
|
||||
let response;
|
||||
|
||||
if (options.method === 'smart') {
|
||||
response = await fetch('/api/smart-clone', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url })
|
||||
});
|
||||
} else {
|
||||
// WGET logic
|
||||
response = await fetch('/api/wget-clone', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url,
|
||||
depth: options.depth
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
clearInterval(progressInterval);
|
||||
clearInterval(messageInterval);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
// Complete progress
|
||||
progressBar.style.width = '100%';
|
||||
progressMessage.textContent = '✅ Clonagem concluída com sucesso!';
|
||||
|
||||
// Update stats
|
||||
// Update stats
|
||||
const files = data.data.resources || data.data.files || 0;
|
||||
const size = data.data.size || 0; // Smart clone might not return size yet
|
||||
|
||||
filesDownloaded.textContent = files;
|
||||
sizeDownloaded.textContent = formatBytes(size);
|
||||
|
||||
// Save to history
|
||||
const cloneRecord = {
|
||||
url,
|
||||
timestamp: new Date().toISOString(),
|
||||
success: true,
|
||||
files: files,
|
||||
size: size,
|
||||
directory: data.data.path || data.data.directory,
|
||||
depth: options.depth,
|
||||
method: options.method
|
||||
};
|
||||
|
||||
saveToHistory(cloneRecord);
|
||||
|
||||
// Show success message
|
||||
setTimeout(() => {
|
||||
showToast('Site clonado com sucesso!', 'success');
|
||||
progressCard.style.display = 'none';
|
||||
|
||||
// Show success dialog
|
||||
showSuccessDialog(cloneRecord);
|
||||
}, 2000);
|
||||
|
||||
} else {
|
||||
throw new Error(data.error || 'Erro desconhecido');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
clearInterval(progressInterval);
|
||||
clearInterval(messageInterval);
|
||||
|
||||
console.error('Clone error:', error);
|
||||
progressCard.style.display = 'none';
|
||||
|
||||
// Save failed attempt to history
|
||||
saveToHistory({
|
||||
url,
|
||||
timestamp: new Date().toISOString(),
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
|
||||
showToast('Erro ao clonar site: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showSuccessDialog(cloneRecord) {
|
||||
const dialog = document.createElement('div');
|
||||
dialog.className = 'success-dialog';
|
||||
dialog.innerHTML = `
|
||||
<div class="success-dialog-content">
|
||||
<div class="success-icon">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<h3>Site Clonado com Sucesso!</h3>
|
||||
<p>${cloneRecord.url}</p>
|
||||
<div class="success-stats">
|
||||
<span><i class="fas fa-file"></i> ${cloneRecord.files} arquivos</span>
|
||||
<span><i class="fas fa-hdd"></i> ${formatBytes(cloneRecord.size)}</span>
|
||||
</div>
|
||||
<div class="success-actions">
|
||||
<button class="btn btn-primary" onclick="openClonedSite('${cloneRecord.directory}'); closeSuccessDialog()">
|
||||
<i class="fas fa-external-link-alt"></i> Abrir Site
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="openCloneFolder('${cloneRecord.directory}'); closeSuccessDialog()">
|
||||
<i class="fas fa-folder-open"></i> Abrir Pasta
|
||||
</button>
|
||||
<button class="btn btn-secondary" onclick="closeSuccessDialog()">
|
||||
Fechar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
// Add styles
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.success-dialog {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10000;
|
||||
animation: fadeIn 0.3s;
|
||||
}
|
||||
.success-dialog-content {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
max-width: 500px;
|
||||
text-align: center;
|
||||
animation: slideUp 0.3s;
|
||||
}
|
||||
.success-icon {
|
||||
font-size: 4rem;
|
||||
color: var(--success-color);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.success-dialog-content h3 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.success-dialog-content p {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.success-stats {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
padding: 1rem;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.success-stats span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.success-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
@keyframes slideUp {
|
||||
from { transform: translateY(20px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
function closeSuccessDialog() {
|
||||
const dialog = document.querySelector('.success-dialog');
|
||||
if (dialog) {
|
||||
dialog.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// History
|
||||
function loadHistory() {
|
||||
const history = JSON.parse(localStorage.getItem('cloneHistory') || '[]');
|
||||
cloneHistory = history;
|
||||
|
||||
const historyList = document.getElementById('historyList');
|
||||
|
||||
if (history.length === 0) {
|
||||
historyList.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-history"></i>
|
||||
<h3>Nenhum histórico</h3>
|
||||
<p>Seus sites clonados aparecerão aqui</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
historyList.innerHTML = history.map((item, index) => `
|
||||
<div class="history-item">
|
||||
<div class="history-info">
|
||||
<h4>${item.url}</h4>
|
||||
<div class="history-meta">
|
||||
<span><i class="fas fa-calendar"></i> ${formatDate(item.timestamp)}</span>
|
||||
<span><i class="fas fa-${item.success ? 'check-circle' : 'times-circle'}"></i> ${item.success ? 'Sucesso' : 'Falhou'}</span>
|
||||
${item.files ? `<span><i class="fas fa-file"></i> ${item.files} arquivos</span>` : ''}
|
||||
${item.size ? `<span><i class="fas fa-hdd"></i> ${formatBytes(item.size)}</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div class="clone-actions">
|
||||
${item.success ? `
|
||||
<button class="btn btn-secondary btn-sm" onclick="openClonedSite('${item.directory}')" title="Abrir Site">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="openCloneFolder('${item.directory}')" title="Abrir Pasta">
|
||||
<i class="fas fa-folder-open"></i>
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="recloneSite('${item.url}')" title="Clonar Novamente">
|
||||
<i class="fas fa-redo"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteHistoryItem(${index})" title="Remover">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function refreshHistory() {
|
||||
loadHistory();
|
||||
showToast('Histórico atualizado!', 'success');
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
if (confirm('Tem certeza que deseja limpar todo o histórico? Esta ação não pode ser desfeita.')) {
|
||||
localStorage.removeItem('cloneHistory');
|
||||
cloneHistory = [];
|
||||
loadHistory();
|
||||
loadDashboard();
|
||||
showToast('Histórico limpo com sucesso!', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
function deleteHistoryItem(index) {
|
||||
if (confirm('Remover este item do histórico?')) {
|
||||
cloneHistory.splice(index, 1);
|
||||
localStorage.setItem('cloneHistory', JSON.stringify(cloneHistory));
|
||||
loadHistory();
|
||||
loadDashboard();
|
||||
showToast('Item removido!', 'success');
|
||||
}
|
||||
}
|
||||
|
||||
function recloneSite(url) {
|
||||
document.getElementById('cloneUrl').value = url;
|
||||
navigateToPage('new-clone');
|
||||
showToast('URL carregada! Configure as opções e clique em "Iniciar Clonagem"', 'info');
|
||||
}
|
||||
|
||||
function saveToHistory(record) {
|
||||
cloneHistory.unshift(record);
|
||||
// Keep only last 100 records
|
||||
if (cloneHistory.length > 100) {
|
||||
cloneHistory = cloneHistory.slice(0, 100);
|
||||
}
|
||||
localStorage.setItem('cloneHistory', JSON.stringify(cloneHistory));
|
||||
}
|
||||
|
||||
// Settings
|
||||
function loadSettings() {
|
||||
const savedSettings = localStorage.getItem('cloneSettings');
|
||||
if (savedSettings) {
|
||||
settings = JSON.parse(savedSettings);
|
||||
}
|
||||
|
||||
// Populate settings form (if needed)
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
localStorage.setItem('cloneSettings', JSON.stringify(settings));
|
||||
showToast('Configurações salvas!', 'success');
|
||||
}
|
||||
|
||||
// Utility Functions
|
||||
function openClonedSite(directory) {
|
||||
// Try to open via server endpoint
|
||||
fetch('http://localhost:8080', {
|
||||
method: 'GET'
|
||||
}).then(response => {
|
||||
if (response.ok) {
|
||||
// Server is running, open the site
|
||||
const siteName = directory.split('\\').pop();
|
||||
window.open(`http://localhost:8080/${siteName}/`, '_blank');
|
||||
} else {
|
||||
showToast('Servidor HTTP não está rodando. Inicie o servidor na porta 8080.', 'error');
|
||||
}
|
||||
}).catch(error => {
|
||||
showToast('Servidor HTTP não está rodando. Inicie o servidor na porta 8080.', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
function openCloneFolder(directory) {
|
||||
showToast('Abrindo pasta: ' + directory, 'info');
|
||||
// In a real app, this would use an API endpoint to open the folder
|
||||
console.log('Open folder:', directory);
|
||||
}
|
||||
|
||||
function showToast(message, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast toast-${type}`;
|
||||
toast.innerHTML = `
|
||||
<i class="fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}"></i>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Add styles if not already added
|
||||
if (!document.getElementById('toast-styles')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'toast-styles';
|
||||
style.textContent = `
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
background: white;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
z-index: 10000;
|
||||
animation: slideInRight 0.3s, slideOutRight 0.3s 2.7s;
|
||||
}
|
||||
.toast-success { border-left: 4px solid var(--success-color); }
|
||||
.toast-error { border-left: 4px solid var(--danger-color); }
|
||||
.toast-info { border-left: 4px solid var(--info-color); }
|
||||
.toast i { font-size: 1.2rem; }
|
||||
.toast-success i { color: var(--success-color); }
|
||||
.toast-error i { color: var(--danger-color); }
|
||||
.toast-info i { color: var(--info-color); }
|
||||
@keyframes slideInRight {
|
||||
from { transform: translateX(400px); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
@keyframes slideOutRight {
|
||||
from { transform: translateX(0); opacity: 1; }
|
||||
to { transform: translateX(400px); opacity: 0; }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('pt-BR', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function formatRelativeTime(dateString) {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diff = now - date;
|
||||
|
||||
const seconds = Math.floor(diff / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
if (days > 0) return days === 1 ? 'Há 1 dia' : `Há ${days} dias`;
|
||||
if (hours > 0) return hours === 1 ? 'Há 1 hora' : `Há ${hours} horas`;
|
||||
if (minutes > 0) return minutes === 1 ? 'Há 1 minuto' : `Há ${minutes} minutos`;
|
||||
return 'Agora mesmo';
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
const urlInput = document.getElementById('urlInput');
|
||||
const startBtn = document.getElementById('startBtn');
|
||||
const heroSection = document.getElementById('hero');
|
||||
const loadingSection = document.getElementById('loading');
|
||||
const resultSection = document.getElementById('result');
|
||||
const terminalLog = document.getElementById('terminalLog');
|
||||
const progressCircle = document.getElementById('progressCircle');
|
||||
|
||||
let clonedData = null;
|
||||
|
||||
startBtn.addEventListener('click', startCloning);
|
||||
urlInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') startCloning();
|
||||
});
|
||||
|
||||
document.getElementById('btnOpenSite').addEventListener('click', () => {
|
||||
if (clonedData && clonedData.data && clonedData.data.path) {
|
||||
// Construct local URL. Server serves /<folderName>/index.html
|
||||
// We know server serves static files from cloned-sites directly if configured?
|
||||
// Wait, server.js serves ../cloned-sites as static.
|
||||
// so path is just /FolderName/index.html
|
||||
const dirName = clonedData.data.path.split('\\').pop().split('/').pop();
|
||||
window.open(`/${dirName}/index.html`, '_blank');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('btnOpenFolder').addEventListener('click', () => {
|
||||
if (clonedData && clonedData.data && clonedData.data.path) {
|
||||
fetch('/api/open-folder', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: clonedData.data.path })
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
async function startCloning() {
|
||||
const url = urlInput.value.trim();
|
||||
if (!url) {
|
||||
showToast('Por favor, insira uma URL válida', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Switch UI
|
||||
heroSection.classList.add('hidden');
|
||||
loadingSection.classList.remove('hidden');
|
||||
|
||||
// Simulate Terminal Logs
|
||||
log('Iniciando puppeteer...');
|
||||
let progress = 0;
|
||||
const progressInterval = setInterval(() => {
|
||||
if (progress < 90) {
|
||||
progress += Math.random() * 5;
|
||||
updateProgress(progress);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
const logMessages = [
|
||||
'Configurando viewport 1920x1080...',
|
||||
'Interceptando requisições de rede...',
|
||||
'Analisando DOM e Shadow DOM...',
|
||||
'Baixando assets estáticos...',
|
||||
'Processando estilos computados...',
|
||||
'Otimizando imagens...',
|
||||
'Verificando integridade...'
|
||||
];
|
||||
|
||||
let msgIndex = 0;
|
||||
const msgInterval = setInterval(() => {
|
||||
if (msgIndex < logMessages.length) {
|
||||
log(logMessages[msgIndex]);
|
||||
msgIndex++;
|
||||
}
|
||||
}, 2500);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/smart-clone', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url })
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
clonedData = data;
|
||||
|
||||
clearInterval(progressInterval);
|
||||
clearInterval(msgInterval);
|
||||
updateProgress(100);
|
||||
|
||||
if (data.success) {
|
||||
log('Clonagem concluída com sucesso!');
|
||||
setTimeout(() => {
|
||||
showResult(data);
|
||||
}, 1000);
|
||||
} else {
|
||||
throw new Error(data.error || 'Erro desconhecido');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
clearInterval(progressInterval);
|
||||
clearInterval(msgInterval);
|
||||
showToast('Erro: ' + error.message, 'error');
|
||||
log('❌ Erro fatal: ' + error.message);
|
||||
setTimeout(() => {
|
||||
heroSection.classList.remove('hidden');
|
||||
loadingSection.classList.add('hidden');
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
function updateProgress(value) {
|
||||
// 283 is the circumference
|
||||
const dashboard = 283;
|
||||
const offset = dashboard - (value / 100) * dashboard;
|
||||
progressCircle.style.strokeDashoffset = offset;
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'log-line';
|
||||
div.innerText = `> ${msg}`;
|
||||
terminalLog.appendChild(div);
|
||||
terminalLog.scrollTop = terminalLog.scrollHeight;
|
||||
}
|
||||
|
||||
function showResult(data) {
|
||||
loadingSection.classList.add('hidden');
|
||||
resultSection.classList.remove('hidden');
|
||||
|
||||
// Handle Verification Images
|
||||
const verifyContainer = document.getElementById('verificationContainer');
|
||||
if (data.data.verification) {
|
||||
verifyContainer.style.display = 'block';
|
||||
|
||||
// Paths are relative to site dir.
|
||||
// We need to construct URL to fetch them.
|
||||
const dirName = data.data.path.split('\\').pop().split('/').pop();
|
||||
|
||||
document.getElementById('imgOriginal').src = `/${dirName}/${data.data.verification.original}`;
|
||||
document.getElementById('imgClone').src = `/${dirName}/${data.data.verification.clone}`;
|
||||
} else {
|
||||
verifyContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function showToast(msg, type = 'info') {
|
||||
const toast = document.createElement('div');
|
||||
toast.style.position = 'fixed';
|
||||
toast.style.bottom = '20px';
|
||||
toast.style.right = '20px';
|
||||
toast.style.background = type === 'error' ? '#ef4444' : '#10b981';
|
||||
toast.style.color = 'white';
|
||||
toast.style.padding = '1rem 2rem';
|
||||
toast.style.borderRadius = '8px';
|
||||
toast.style.zIndex = '1000';
|
||||
toast.innerText = msg;
|
||||
document.body.appendChild(toast);
|
||||
setTimeout(() => toast.remove(), 3000);
|
||||
}
|
||||
@@ -0,0 +1,452 @@
|
||||
// Global state
|
||||
let currentResults = null;
|
||||
|
||||
// DOM Elements
|
||||
const crawlerForm = document.getElementById('crawlerForm');
|
||||
const urlInput = document.getElementById('urlInput');
|
||||
const crawlBtn = document.getElementById('crawlBtn');
|
||||
const loadingSection = document.getElementById('loadingSection');
|
||||
const resultsSection = document.getElementById('resultsSection');
|
||||
const errorSection = document.getElementById('errorSection');
|
||||
const crawlerStatus = document.getElementById('crawlerStatus');
|
||||
const exportBtn = document.getElementById('exportBtn');
|
||||
|
||||
// Initialize app
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
checkCrawlerStatus();
|
||||
setupEventListeners();
|
||||
setupTabs();
|
||||
});
|
||||
|
||||
// Check crawler service status
|
||||
async function checkCrawlerStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/health');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'ok') {
|
||||
updateStatusIndicator('online', 'Crawler Online');
|
||||
} else {
|
||||
updateStatusIndicator('offline', 'Crawler Offline');
|
||||
}
|
||||
} catch (error) {
|
||||
updateStatusIndicator('offline', 'Crawler Offline');
|
||||
}
|
||||
}
|
||||
|
||||
// Update status indicator
|
||||
function updateStatusIndicator(status, text) {
|
||||
crawlerStatus.className = `status-indicator ${status}`;
|
||||
crawlerStatus.querySelector('span').textContent = text;
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
function setupEventListeners() {
|
||||
crawlerForm.addEventListener('submit', handleCrawlSubmit);
|
||||
exportBtn.addEventListener('click', exportResults);
|
||||
|
||||
// Auto-check status every 30 seconds
|
||||
setInterval(checkCrawlerStatus, 30000);
|
||||
}
|
||||
|
||||
// Setup tabs functionality
|
||||
function setupTabs() {
|
||||
const tabButtons = document.querySelectorAll('.tab-btn');
|
||||
const tabPanes = document.querySelectorAll('.tab-pane');
|
||||
|
||||
tabButtons.forEach(button => {
|
||||
button.addEventListener('click', () => {
|
||||
const targetTab = button.getAttribute('data-tab');
|
||||
|
||||
// Remove active class from all buttons and panes
|
||||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
||||
tabPanes.forEach(pane => pane.classList.remove('active'));
|
||||
|
||||
// Add active class to clicked button and corresponding pane
|
||||
button.classList.add('active');
|
||||
document.getElementById(targetTab).classList.add('active');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle crawl form submission
|
||||
async function handleCrawlSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const url = urlInput.value.trim();
|
||||
if (!url) {
|
||||
showError('Por favor, insira uma URL válida.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate URL format
|
||||
try {
|
||||
new URL(url);
|
||||
} catch {
|
||||
showError('Por favor, insira uma URL válida (ex: https://example.com).');
|
||||
return;
|
||||
}
|
||||
|
||||
await startCrawling(url);
|
||||
}
|
||||
|
||||
// Start crawling process
|
||||
async function startCrawling(url) {
|
||||
showLoading();
|
||||
updateLoadingMessage('🚀 Iniciando clonagem profissional com WGET...');
|
||||
|
||||
try {
|
||||
// SEM TIMEOUT - permite clonagens longas
|
||||
// Removido AbortController para permitir clonagens que demoram mais de 10 minutos
|
||||
|
||||
// Atualizar mensagem periodicamente
|
||||
const messages = [
|
||||
'🔍 Analisando estrutura do site...',
|
||||
'📄 Baixando páginas HTML...',
|
||||
'📦 Baixando imagens e assets...',
|
||||
'🎨 Baixando CSS e fontes...',
|
||||
'⚡ Baixando JavaScript...',
|
||||
'🔗 Convertendo links para locais...',
|
||||
'⏳ Aguarde, clonagem profissional em andamento...'
|
||||
];
|
||||
let messageIndex = 0;
|
||||
const messageInterval = setInterval(() => {
|
||||
messageIndex = (messageIndex + 1) % messages.length;
|
||||
updateLoadingMessage(messages[messageIndex]);
|
||||
}, 4000);
|
||||
|
||||
// Tentar usar wget primeiro
|
||||
let response;
|
||||
try {
|
||||
updateLoadingMessage('🔧 Usando WGET para clonagem profissional...');
|
||||
response = await fetch('/api/wget-clone', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ url })
|
||||
// SEM signal/timeout - permite clonagens longas
|
||||
});
|
||||
} catch (wgetError) {
|
||||
// Se wget falhar, usar crawler normal
|
||||
console.log('WGET não disponível, usando crawler padrão');
|
||||
updateLoadingMessage('🔄 Usando crawler alternativo...');
|
||||
response = await fetch('/api/crawl', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ url })
|
||||
// SEM signal/timeout - permite clonagens longas
|
||||
});
|
||||
}
|
||||
|
||||
// Removido clearTimeout pois não há mais timeout
|
||||
clearInterval(messageInterval);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
updateLoadingMessage('✅ Processando resultados...');
|
||||
setTimeout(() => {
|
||||
currentResults = data.data;
|
||||
showResults(data.data);
|
||||
}, 1000);
|
||||
} else {
|
||||
throw new Error(data.error || 'Erro desconhecido no crawling');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Crawling error:', error);
|
||||
if (error.name === 'AbortError') {
|
||||
showError('⏱️ Timeout: A clonagem demorou mais de 10 minutos. Tente novamente com um site menor.');
|
||||
} else {
|
||||
showError(error.message || 'Erro ao conectar com o serviço de crawling');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
function showLoading() {
|
||||
hideAllSections();
|
||||
loadingSection.style.display = 'block';
|
||||
}
|
||||
|
||||
// Update loading message
|
||||
function updateLoadingMessage(message) {
|
||||
const loadingMessage = document.getElementById('loadingMessage');
|
||||
if (loadingMessage) {
|
||||
loadingMessage.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
// Show results
|
||||
function showResults(data) {
|
||||
hideAllSections();
|
||||
resultsSection.style.display = 'block';
|
||||
|
||||
// Populate stats
|
||||
populateStats(data);
|
||||
|
||||
// Populate overview
|
||||
populateOverview(data);
|
||||
|
||||
// Populate links
|
||||
populateLinks(data.links || []);
|
||||
|
||||
// Populate images
|
||||
populateImages(data.images || []);
|
||||
|
||||
// Populate raw data
|
||||
populateRawData(data);
|
||||
}
|
||||
|
||||
// Populate statistics
|
||||
function populateStats(data) {
|
||||
const statsGrid = document.getElementById('statsGrid');
|
||||
|
||||
const stats = [
|
||||
{
|
||||
label: 'Status Code',
|
||||
value: data.statusCode || 'N/A',
|
||||
icon: 'fas fa-check-circle'
|
||||
},
|
||||
{
|
||||
label: 'Links Encontrados',
|
||||
value: (data.links || []).length,
|
||||
icon: 'fas fa-link'
|
||||
},
|
||||
{
|
||||
label: 'Imagens Encontradas',
|
||||
value: (data.images || []).length,
|
||||
icon: 'fas fa-image'
|
||||
},
|
||||
{
|
||||
label: 'Tamanho do Conteúdo',
|
||||
value: formatBytes(data.contentLength || 0),
|
||||
icon: 'fas fa-file-alt'
|
||||
}
|
||||
];
|
||||
|
||||
statsGrid.innerHTML = stats.map(stat => `
|
||||
<div class="stat-card">
|
||||
<i class="${stat.icon}" style="font-size: 1.5rem; color: #667eea; margin-bottom: 0.5rem;"></i>
|
||||
<span class="stat-value">${stat.value}</span>
|
||||
<div class="stat-label">${stat.label}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Populate overview
|
||||
function populateOverview(data) {
|
||||
const overviewContent = document.querySelector('#overview .overview-content');
|
||||
|
||||
const overviewItems = [
|
||||
{ label: 'URL', value: data.url || 'N/A' },
|
||||
{ label: 'Título', value: data.title || 'N/A' },
|
||||
{ label: 'Status Code', value: data.statusCode || 'N/A' },
|
||||
{ label: 'Timestamp', value: formatDate(data.timestamp) },
|
||||
{ label: 'Tamanho do Conteúdo', value: formatBytes(data.contentLength || 0) },
|
||||
{ label: 'Total de Links', value: (data.links || []).length },
|
||||
{ label: 'Total de Imagens', value: (data.images || []).length }
|
||||
];
|
||||
|
||||
// Adicionar informações do clone se disponível
|
||||
if (data.cloneInfo) {
|
||||
overviewItems.push(
|
||||
{ label: 'Clone Status', value: data.cloneInfo.success ? '✅ Sucesso' : '❌ Falhou' },
|
||||
{ label: 'Assets Baixados', value: `${data.cloneInfo.assetsDownloaded}/${data.cloneInfo.totalAssets}` },
|
||||
{ label: 'Diretório Local', value: data.cloneInfo.directory }
|
||||
);
|
||||
}
|
||||
|
||||
overviewContent.innerHTML = overviewItems.map(item => `
|
||||
<div class="overview-item">
|
||||
<span class="label">${item.label}:</span>
|
||||
<span class="value">${item.value}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
|
||||
// Adicionar botão para abrir pasta do clone
|
||||
if (data.cloneInfo && data.cloneInfo.success) {
|
||||
const cloneButton = document.createElement('div');
|
||||
cloneButton.className = 'clone-actions';
|
||||
cloneButton.innerHTML = `
|
||||
<div style="margin-top: 1rem; padding: 1rem; background: #e8f5e8; border-radius: 8px; border-left: 4px solid #28a745;">
|
||||
<h4 style="color: #155724; margin-bottom: 0.5rem;">🎉 Site Clonado com Sucesso!</h4>
|
||||
<p style="color: #155724; margin-bottom: 1rem;">O site foi clonado e salvo localmente com todos os assets.</p>
|
||||
<p style="color: #155724; font-size: 0.9rem; margin-bottom: 1rem;"><strong>Localização:</strong> ${data.cloneInfo.directory}</p>
|
||||
<div style="display: flex; gap: 0.5rem; flex-wrap: wrap;">
|
||||
<button class="btn btn-success btn-sm" onclick="openCloneFolder('${data.cloneInfo.directory}')">
|
||||
📁 Abrir Pasta
|
||||
</button>
|
||||
<button class="btn btn-primary btn-sm" onclick="openClonedSite('${data.cloneInfo.directory}')">
|
||||
🌐 Abrir Site Local
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
overviewContent.appendChild(cloneButton);
|
||||
}
|
||||
}
|
||||
|
||||
// Populate links
|
||||
function populateLinks(links) {
|
||||
const linksList = document.querySelector('#links .links-list');
|
||||
|
||||
if (links.length === 0) {
|
||||
linksList.innerHTML = '<p style="text-align: center; color: #666; padding: 2rem;">Nenhum link encontrado.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
linksList.innerHTML = links.map(link => `
|
||||
<div class="link-item">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
<a href="${link}" target="_blank" rel="noopener noreferrer">${link}</a>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Populate images
|
||||
function populateImages(images) {
|
||||
const imagesGrid = document.querySelector('#images .images-grid');
|
||||
|
||||
if (images.length === 0) {
|
||||
imagesGrid.innerHTML = '<p style="text-align: center; color: #666; padding: 2rem; grid-column: 1 / -1;">Nenhuma imagem encontrada.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
imagesGrid.innerHTML = images.map(imageUrl => `
|
||||
<div class="image-item">
|
||||
<img src="${imageUrl}" alt="Website Image" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
|
||||
<div style="display: none; padding: 2rem; color: #666;">
|
||||
<i class="fas fa-image" style="font-size: 2rem; margin-bottom: 1rem;"></i>
|
||||
<p>Imagem não pôde ser carregada</p>
|
||||
</div>
|
||||
<div class="image-url">${imageUrl}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// Populate raw data
|
||||
function populateRawData(data) {
|
||||
const rawDataElement = document.getElementById('rawData');
|
||||
rawDataElement.textContent = JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
// Show error
|
||||
function showError(message) {
|
||||
hideAllSections();
|
||||
errorSection.style.display = 'block';
|
||||
document.getElementById('errorMessage').textContent = message;
|
||||
}
|
||||
|
||||
// Hide all sections
|
||||
function hideAllSections() {
|
||||
loadingSection.style.display = 'none';
|
||||
resultsSection.style.display = 'none';
|
||||
errorSection.style.display = 'none';
|
||||
}
|
||||
|
||||
// Reset form
|
||||
function resetForm() {
|
||||
hideAllSections();
|
||||
urlInput.value = '';
|
||||
urlInput.focus();
|
||||
currentResults = null;
|
||||
}
|
||||
|
||||
// Export results
|
||||
function exportResults() {
|
||||
if (!currentResults) {
|
||||
alert('Nenhum resultado para exportar.');
|
||||
return;
|
||||
}
|
||||
|
||||
const dataStr = JSON.stringify(currentResults, null, 2);
|
||||
const dataBlob = new Blob([dataStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(dataBlob);
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `crawl-results-${new Date().toISOString().split('T')[0]}.json`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
if (!dateString) return 'N/A';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('pt-BR');
|
||||
}
|
||||
|
||||
// Add some sample URLs for quick testing
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const urlInput = document.getElementById('urlInput');
|
||||
|
||||
// Add placeholder with sample URLs
|
||||
const sampleUrls = [
|
||||
'https://example.com',
|
||||
'https://github.com',
|
||||
'https://stackoverflow.com',
|
||||
'https://www.uol.com.br'
|
||||
];
|
||||
|
||||
let currentSample = 0;
|
||||
urlInput.placeholder = sampleUrls[currentSample];
|
||||
|
||||
// Cycle through sample URLs in placeholder
|
||||
setInterval(() => {
|
||||
currentSample = (currentSample + 1) % sampleUrls.length;
|
||||
if (!urlInput.value) {
|
||||
urlInput.placeholder = sampleUrls[currentSample];
|
||||
}
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
// Funções para gerenciar sites clonados
|
||||
function openCloneFolder(directory) {
|
||||
// Tentar abrir a pasta no explorador do Windows
|
||||
fetch('/api/open-folder', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ path: directory })
|
||||
}).then(response => {
|
||||
if (!response.ok) {
|
||||
alert('Não foi possível abrir a pasta automaticamente.\nLocalização: ' + directory);
|
||||
}
|
||||
}).catch(error => {
|
||||
alert('Não foi possível abrir a pasta automaticamente.\nLocalização: ' + directory);
|
||||
});
|
||||
}
|
||||
|
||||
function openClonedSite(directory) {
|
||||
// Tentar abrir o index.html do site clonado
|
||||
fetch('/api/open-site', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ path: directory })
|
||||
}).then(response => {
|
||||
if (!response.ok) {
|
||||
alert('Não foi possível abrir o site automaticamente.\nAbra manualmente: ' + directory + '\\index.html');
|
||||
}
|
||||
}).catch(error => {
|
||||
alert('Não foi possível abrir o site automaticamente.\nAbra manualmente: ' + directory + '\\index.html');
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* Fallback icons using Unicode symbols */
|
||||
.fas.fa-spider::before { content: "🕷️"; }
|
||||
.fas.fa-circle::before { content: "●"; }
|
||||
.fas.fa-globe::before { content: "🌐"; }
|
||||
.fas.fa-link::before { content: "🔗"; }
|
||||
.fas.fa-chart-line::before { content: "📊"; }
|
||||
.fas.fa-download::before { content: "⬇️"; }
|
||||
.fas.fa-check-circle::before { content: "✅"; }
|
||||
.fas.fa-image::before { content: "🖼️"; }
|
||||
.fas.fa-file-alt::before { content: "📄"; }
|
||||
.fas.fa-external-link-alt::before { content: "↗️"; }
|
||||
.fas.fa-exclamation-triangle::before { content: "⚠️"; }
|
||||
.fas.fa-redo::before { content: "🔄"; }
|
||||
|
||||
/* Remove font-family for fallback */
|
||||
.fas {
|
||||
font-family: inherit !important;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||
<circle cx="16" cy="16" r="14" fill="#667eea"/>
|
||||
<text x="16" y="22" text-anchor="middle" fill="white" font-size="16" font-family="Arial">🕷️</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 223 B |
@@ -0,0 +1,297 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CloneWeb Pro - Clonagem Profissional de Sites</title>
|
||||
<link rel="stylesheet" href="styles-new.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar">
|
||||
<div class="logo">
|
||||
<i class="fas fa-clone"></i>
|
||||
<h1>CloneWeb Pro</h1>
|
||||
</div>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<a href="#" class="nav-item active" data-page="dashboard">
|
||||
<i class="fas fa-home"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-page="new-clone">
|
||||
<i class="fas fa-plus-circle"></i>
|
||||
<span>Nova Clonagem</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-page="history">
|
||||
<i class="fas fa-history"></i>
|
||||
<span>Histórico</span>
|
||||
</a>
|
||||
<a href="#" class="nav-item" data-page="settings">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span>Configurações</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="theme-toggle">
|
||||
<i class="fas fa-moon"></i>
|
||||
<span>Modo Escuro</span>
|
||||
<label class="switch">
|
||||
<input type="checkbox" id="themeToggle">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="header-left">
|
||||
<h2 id="pageTitle">Dashboard</h2>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="status-indicator" id="serviceStatus">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">Verificando...</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Dashboard Page -->
|
||||
<div class="page active" id="dashboard">
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);">
|
||||
<i class="fas fa-globe"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<h3 id="totalSites">0</h3>
|
||||
<p>Sites Clonados</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">
|
||||
<i class="fas fa-database"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<h3 id="totalSize">0 MB</h3>
|
||||
<p>Espaço Utilizado</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);">
|
||||
<i class="fas fa-clock"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<h3 id="lastClone">Nunca</h3>
|
||||
<p>Última Clonagem</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon" style="background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<h3 id="successRate">100%</h3>
|
||||
<p>Taxa de Sucesso</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="recent-clones">
|
||||
<div class="section-header">
|
||||
<h3>Sites Clonados Recentemente</h3>
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshDashboard()">
|
||||
<i class="fas fa-sync-alt"></i> Atualizar
|
||||
</button>
|
||||
</div>
|
||||
<div id="recentClonesList" class="clones-list">
|
||||
<!-- Será preenchido dinamicamente -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Clone Page -->
|
||||
<div class="page" id="new-clone">
|
||||
<div class="clone-form-container">
|
||||
<div class="form-card">
|
||||
<h3>Nova Clonagem de Site</h3>
|
||||
<p class="form-description">Insira a URL do site que deseja clonar e configure as opções avançadas.</p>
|
||||
|
||||
<form id="cloneForm">
|
||||
<div class="form-group">
|
||||
<label for="cloneUrl">
|
||||
<i class="fas fa-link"></i> URL do Site
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
id="cloneUrl"
|
||||
class="form-control"
|
||||
placeholder="https://exemplo.com.br"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<i class="fas fa-sliders-h"></i> Configurações Avançadas
|
||||
</label>
|
||||
<div class="advanced-options">
|
||||
<div class="option-row">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="includeImages" checked>
|
||||
<span>Incluir Imagens</span>
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="includeCSS" checked>
|
||||
<span>Incluir CSS</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option-row">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="includeJS" checked>
|
||||
<span>Incluir JavaScript</span>
|
||||
</label>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="convertLinks" checked>
|
||||
<span>Converter Links</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="depthLevel">
|
||||
<i class="fas fa-layer-group"></i> Profundidade de Clonagem
|
||||
</label>
|
||||
<div class="slider-container">
|
||||
<input type="range" id="depthLevel" min="1" max="5" value="3" class="slider-input">
|
||||
<span class="slider-value" id="depthValue">3 níveis</span>
|
||||
</div>
|
||||
<small>Controla quantas páginas vinculadas serão clonadas</small>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary btn-lg">
|
||||
<i class="fas fa-download"></i> Iniciar Clonagem
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Progress Card -->
|
||||
<div class="progress-card" id="progressCard" style="display: none;">
|
||||
<h3>Clonagem em Andamento</h3>
|
||||
<div class="progress-info">
|
||||
<div class="progress-status">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
<span id="progressMessage">Iniciando clonagem...</span>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<div class="progress-bar" id="progressBar"></div>
|
||||
</div>
|
||||
<div class="progress-stats">
|
||||
<span><i class="fas fa-file"></i> <span id="filesDownloaded">0</span> arquivos</span>
|
||||
<span><i class="fas fa-hdd"></i> <span id="sizeDownloaded">0 MB</span></span>
|
||||
<span><i class="fas fa-clock"></i> <span id="timeElapsed">0s</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- History Page -->
|
||||
<div class="page" id="history">
|
||||
<div class="section-header">
|
||||
<h3>Histórico de Clonagens</h3>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshHistory()">
|
||||
<i class="fas fa-sync-alt"></i> Atualizar
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="clearHistory()">
|
||||
<i class="fas fa-trash"></i> Limpar Histórico
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="history-list" id="historyList">
|
||||
<!-- Será preenchido dinamicamente -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Settings Page -->
|
||||
<div class="page" id="settings">
|
||||
<div class="settings-container">
|
||||
<div class="settings-section">
|
||||
<h3><i class="fas fa-cog"></i> Configurações Gerais</h3>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<h4>Diretório de Saída</h4>
|
||||
<p>Local onde os sites clonados serão salvos</p>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<input type="text" class="form-control" value="M:\CLONEWEB\cloned-sites" readonly>
|
||||
<button class="btn btn-secondary btn-sm">
|
||||
<i class="fas fa-folder-open"></i> Abrir
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<h4>Timeout de Requisição</h4>
|
||||
<p>Tempo máximo de espera por arquivo (segundos)</p>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<input type="number" class="form-control" value="30" min="10" max="120">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<h4>Tentativas de Download</h4>
|
||||
<p>Número de tentativas para arquivos que falharem</p>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<input type="number" class="form-control" value="3" min="1" max="10">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h3><i class="fas fa-ban"></i> Exclusões</h3>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<h4>Domínios Excluídos</h4>
|
||||
<p>Domínios que não serão clonados (um por linha)</p>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<textarea class="form-control" rows="5" placeholder="facebook.com twitter.com instagram.com"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-actions">
|
||||
<button class="btn btn-primary">
|
||||
<i class="fas fa-save"></i> Salvar Configurações
|
||||
</button>
|
||||
<button class="btn btn-secondary">
|
||||
<i class="fas fa-undo"></i> Restaurar Padrões
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script src="app-new.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self' http://localhost:3001; font-src 'self';">
|
||||
<title>CloneWeb - Website Crawler Platform</title>
|
||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||
<link rel="stylesheet" href="fallback-icons.css">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<!-- Header -->
|
||||
<header class="header">
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<i class="fas fa-spider"></i>
|
||||
<h1>CloneWeb</h1>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<div class="status-indicator" id="crawlerStatus">
|
||||
<i class="fas fa-circle"></i>
|
||||
<span>Verificando...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main">
|
||||
<div class="container">
|
||||
<!-- Hero Section -->
|
||||
<section class="hero">
|
||||
<h2>Plataforma Inteligente de Crawling</h2>
|
||||
<p>Extraia dados de websites com precisão usando nossa tecnologia avançada de crawling</p>
|
||||
</section>
|
||||
|
||||
<!-- Crawler Form -->
|
||||
<section class="crawler-section">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-globe"></i> Iniciar Crawling</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form id="crawlerForm">
|
||||
<div class="form-group">
|
||||
<label for="urlInput">URL do Website</label>
|
||||
<div class="input-group">
|
||||
<i class="fas fa-link"></i>
|
||||
<input
|
||||
type="url"
|
||||
id="urlInput"
|
||||
placeholder="https://example.com"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary" id="crawlBtn">
|
||||
<i class="fas fa-spider"></i>
|
||||
Iniciar Crawling
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Loading State -->
|
||||
<section class="loading-section" id="loadingSection" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<div class="spinner"></div>
|
||||
<h3>Crawling em Progresso...</h3>
|
||||
<p id="loadingMessage">Analisando o website...</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Results Section -->
|
||||
<section class="results-section" id="resultsSection" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-chart-line"></i> Resultados do Crawling</h3>
|
||||
<button class="btn btn-secondary btn-sm" id="exportBtn">
|
||||
<i class="fas fa-download"></i>
|
||||
Exportar JSON
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Summary Stats -->
|
||||
<div class="stats-grid" id="statsGrid">
|
||||
<!-- Stats will be populated by JavaScript -->
|
||||
</div>
|
||||
|
||||
<!-- Detailed Results -->
|
||||
<div class="results-tabs">
|
||||
<div class="tab-buttons">
|
||||
<button class="tab-btn active" data-tab="overview">Visão Geral</button>
|
||||
<button class="tab-btn" data-tab="links">Links</button>
|
||||
<button class="tab-btn" data-tab="images">Imagens</button>
|
||||
<button class="tab-btn" data-tab="raw">Dados Brutos</button>
|
||||
</div>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="overview">
|
||||
<div class="overview-content">
|
||||
<!-- Overview content will be populated -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="links">
|
||||
<div class="links-list">
|
||||
<!-- Links will be populated -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="images">
|
||||
<div class="images-grid">
|
||||
<!-- Images will be populated -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="raw">
|
||||
<pre class="json-output" id="rawData">
|
||||
<!-- Raw JSON will be populated -->
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Error Section -->
|
||||
<section class="error-section" id="errorSection" style="display: none;">
|
||||
<div class="card error-card">
|
||||
<div class="card-body text-center">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<h3>Erro no Crawling</h3>
|
||||
<p id="errorMessage">Ocorreu um erro durante o processo de crawling.</p>
|
||||
<button class="btn btn-primary" onclick="resetForm()">
|
||||
<i class="fas fa-redo"></i>
|
||||
Tentar Novamente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2025 CloneWeb Platform. Desenvolvido com ❤️ para crawling inteligente.</p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,123 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CloneWeb Premium</title>
|
||||
<link rel="stylesheet" href="styles-premium.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="background-effects">
|
||||
<div class="glow-orb orb-1"></div>
|
||||
<div class="glow-orb orb-2"></div>
|
||||
<div class="grid-background"></div>
|
||||
</div>
|
||||
|
||||
<main class="container">
|
||||
<!-- Hero / Input Section -->
|
||||
<section class="hero-section" id="hero">
|
||||
<div class="logo-area">
|
||||
<i class="fas fa-bolt logo-icon"></i>
|
||||
<h1>CloneWeb <span class="badge">PRO</span></h1>
|
||||
<p>Clonagem de alta fidelidade com Inteligência Artificial</p>
|
||||
</div>
|
||||
|
||||
<div class="clone-input-wrapper">
|
||||
<div class="input-group">
|
||||
<i class="fas fa-link input-icon"></i>
|
||||
<input type="url" id="urlInput" placeholder="Cole o link do site aqui..." autocomplete="off">
|
||||
<button id="startBtn" class="action-btn">
|
||||
<span>CLONAR</span>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Remove toggle if we want to force smart mode or hide complex options -->
|
||||
<div class="options-toggle hidden">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="smartMode" checked>
|
||||
<span class="slider"></span>
|
||||
<span class="label-text">Smart Mode (IA)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="features-pill">
|
||||
<span><i class="fas fa-magic"></i> Smart Clone</span>
|
||||
<span><i class="fas fa-image"></i> Verificação Visual</span>
|
||||
<span><i class="fas fa-code"></i> HTML5/CSS3</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Loading State -->
|
||||
<section class="loading-section hidden" id="loading">
|
||||
<div class="loader-circle">
|
||||
<svg viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="45" class="bg"></circle>
|
||||
<circle cx="50" cy="50" r="45" class="fg" id="progressCircle"></circle>
|
||||
</svg>
|
||||
<div class="loader-icon">
|
||||
<i class="fas fa-dna fa-spin animated-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<h2 id="loadingText">Iniciando Motor IA...</h2>
|
||||
<p id="loadingSubtext">Preparando ambiente de clonagem</p>
|
||||
|
||||
<div class="terminal-log" id="terminalLog">
|
||||
<div class="log-line">> Sistema inicializado</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Result / Verification State -->
|
||||
<section class="result-section hidden" id="result">
|
||||
<div class="success-header">
|
||||
<div class="checkmark-circle">
|
||||
<i class="fas fa-check"></i>
|
||||
</div>
|
||||
<h2>Clonagem Concluída!</h2>
|
||||
</div>
|
||||
|
||||
<div class="verification-container hidden" id="verificationContainer">
|
||||
<h3 class="verify-title">Verificação de Qualidade</h3>
|
||||
<div class="comparison-card">
|
||||
<div class="image-wrapper">
|
||||
<span class="img-label">Site Original</span>
|
||||
<img id="imgOriginal" src="" alt="Original">
|
||||
</div>
|
||||
<div class="vs-badge">VS</div>
|
||||
<div class="image-wrapper">
|
||||
<span class="img-label lg-accent">Clone Local</span>
|
||||
<img id="imgClone" src="" alt="Clone">
|
||||
</div>
|
||||
</div>
|
||||
<p class="verification-note">As imagens acima mostram a comparação visual automática.</p>
|
||||
</div>
|
||||
|
||||
<div class="actions-grid">
|
||||
<button class="btn-secondary" id="btnOpenFolder">
|
||||
<i class="fas fa-folder-open"></i> Abrir Pasta
|
||||
</button>
|
||||
<button class="btn-primary glow" id="btnOpenSite">
|
||||
<i class="fas fa-external-link-alt"></i> Abrir Site
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="retry-area">
|
||||
<button class="btn-text" onclick="window.location.reload()">
|
||||
<i class="fas fa-undo"></i> Clonar outro site
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
|
||||
<script src="app-premium.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,740 @@
|
||||
/* Reset e Variáveis */
|
||||
:root {
|
||||
--primary-color: #667eea;
|
||||
--secondary-color: #764ba2;
|
||||
--success-color: #43e97b;
|
||||
--danger-color: #f5576c;
|
||||
--warning-color: #ffa726;
|
||||
--info-color: #4facfe;
|
||||
|
||||
--bg-primary: #f8f9fa;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-sidebar: #1a1d29;
|
||||
|
||||
--text-primary: #2d3748;
|
||||
--text-secondary: #718096;
|
||||
--text-light: #a0aec0;
|
||||
|
||||
--border-color: #e2e8f0;
|
||||
--shadow-sm: 0 2px 4px rgba(0,0,0,0.05);
|
||||
--shadow-md: 0 4px 6px rgba(0,0,0,0.07);
|
||||
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
|
||||
|
||||
--sidebar-width: 260px;
|
||||
--header-height: 70px;
|
||||
|
||||
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Sidebar */
|
||||
.sidebar {
|
||||
width: var(--sidebar-width);
|
||||
background: var(--bg-sidebar);
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.logo {
|
||||
padding: 2rem 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.logo i {
|
||||
font-size: 2rem;
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
flex: 1;
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem 1.5rem;
|
||||
color: rgba(255,255,255,0.7);
|
||||
text-decoration: none;
|
||||
transition: var(--transition);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
background: rgba(255,255,255,0.05);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.nav-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 4px;
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
font-size: 1.2rem;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: 1.5rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
color: rgba(255,255,255,0.7);
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255,255,255,0.2);
|
||||
transition: var(--transition);
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: var(--transition);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
margin-left: var(--sidebar-width);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
height: var(--header-height);
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding: 0 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.header h2 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--text-light);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-indicator.online .status-dot {
|
||||
background: var(--success-color);
|
||||
}
|
||||
|
||||
.status-indicator.offline .status-dot {
|
||||
background: var(--danger-color);
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* Pages */
|
||||
.page {
|
||||
display: none;
|
||||
padding: 2rem;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.page.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.stat-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.stat-content h3 {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.stat-content p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Section Header */
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.section-header h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Recent Clones */
|
||||
.recent-clones {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.clones-list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.clone-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 8px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.clone-item:hover {
|
||||
background: #e8eaf0;
|
||||
}
|
||||
|
||||
.clone-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.clone-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.clone-details h4 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.clone-details p {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.clone-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
.clone-form-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-card h3 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-description {
|
||||
color: var(--text-secondary);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-group label i {
|
||||
margin-right: 0.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
transition: var(--transition);
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
background: white;
|
||||
}
|
||||
|
||||
.advanced-options {
|
||||
background: var(--bg-primary);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.option-row {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.option-row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-label input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.slider-input {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: var(--border-color);
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
.slider-input::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.slider-value {
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
font-weight: 600;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-group small {
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #e2e8f0;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #e04560;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-lg {
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
/* Progress Card */
|
||||
.progress-card {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.progress-card h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.progress-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
height: 8px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
|
||||
width: 0%;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.progress-stats span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.progress-stats i {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* History */
|
||||
.history-list {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.history-item {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.history-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.history-info h4 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.history-meta {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.history-meta span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Settings */
|
||||
.settings-container {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: var(--shadow-md);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.settings-section h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.setting-item {
|
||||
padding: 1.5rem 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.setting-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.setting-info h4 {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.setting-info p {
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.setting-control {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.settings-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.main-content {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.option-row {
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.empty-state i {
|
||||
font-size: 4rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
:root {
|
||||
--bg-dark: #020617;
|
||||
--bg-card: rgba(15, 23, 42, 0.8);
|
||||
--accent-primary: #06b6d4;
|
||||
--accent-secondary: #3b82f6;
|
||||
--accent-glow: rgba(6, 182, 212, 0.4);
|
||||
--text-main: #f8fafc;
|
||||
--text-dim: #94a3b8;
|
||||
--border-glass: rgba(255, 255, 255, 0.1);
|
||||
--glass-blur: blur(12px);
|
||||
--success: #10b981;
|
||||
--error: #ef4444;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Outfit', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background: radial-gradient(circle at 50% 50%, #0f172a 0%, #020617 100%);
|
||||
color: var(--text-main);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow-x: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Background Effects */
|
||||
.background-effects {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.glow-orb {
|
||||
position: absolute;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 70%);
|
||||
filter: blur(80px);
|
||||
opacity: 0.5;
|
||||
animation: orbFloat 20s infinite alternate;
|
||||
}
|
||||
|
||||
.orb-1 {
|
||||
top: -10%;
|
||||
right: -5%;
|
||||
}
|
||||
|
||||
.orb-2 {
|
||||
bottom: -10%;
|
||||
left: -5%;
|
||||
animation-delay: -5s;
|
||||
}
|
||||
|
||||
.grid-background {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.02) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
|
||||
-webkit-mask-image: radial-gradient(circle at center, black, transparent 80%);
|
||||
mask-image: radial-gradient(circle at center, black, transparent 80%);
|
||||
}
|
||||
|
||||
@keyframes orbFloat {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: translate(-50px, 50px) scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero-section {
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.logo-area {
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 4rem;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
filter: drop-shadow(0 0 30px var(--accent-glow));
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 4rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: -2px;
|
||||
background: linear-gradient(to bottom, #fff, #94a3b8);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.9rem;
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
padding: 0.3rem 0.8rem;
|
||||
border-radius: 99px;
|
||||
vertical-align: middle;
|
||||
margin-left: 1rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
-webkit-text-fill-color: white;
|
||||
font-weight: 700;
|
||||
box-shadow: 0 0 20px var(--accent-glow);
|
||||
}
|
||||
|
||||
.logo-area p {
|
||||
color: var(--text-dim);
|
||||
font-size: 1.4rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* Input Group */
|
||||
.clone-input-wrapper {
|
||||
background: var(--bg-card);
|
||||
padding: 1.5rem;
|
||||
border-radius: 24px;
|
||||
border: 1px solid var(--border-glass);
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
|
||||
-webkit-backdrop-filter: var(--glass-blur);
|
||||
backdrop-filter: var(--glass-blur);
|
||||
margin-bottom: 3rem;
|
||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
.clone-input-wrapper:hover {
|
||||
transform: translateY(-5px) scale(1.02);
|
||||
border-color: rgba(6, 182, 212, 0.4);
|
||||
box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.6), 0 0 20px rgba(6, 182, 212, 0.1);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 12px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.input-group:focus-within {
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px rgba(6, 182, 212, 0.2);
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.2rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
input[type="url"] {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.1rem;
|
||||
padding: 1rem 0;
|
||||
outline: none;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 1.2rem 2.5rem;
|
||||
border-radius: 16px;
|
||||
font-weight: 700;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.8rem;
|
||||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
box-shadow: 0 10px 20px -5px var(--accent-glow);
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
transform: translateX(5px) scale(1.05);
|
||||
box-shadow: 0 15px 30px -5px var(--accent-glow);
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
color: var(--accent-primary);
|
||||
font-size: 1.4rem;
|
||||
padding: 0 1.5rem;
|
||||
filter: drop-shadow(0 0 10px var(--accent-glow));
|
||||
}
|
||||
|
||||
input[type="url"] {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
padding: 1.2rem 0;
|
||||
outline: none;
|
||||
min-width: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
input[type="url"]::placeholder {
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.features-pill {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 3rem;
|
||||
color: var(--text-dim);
|
||||
font-size: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.features-pill span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.features-pill span:hover {
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.features-pill i {
|
||||
color: var(--accent-primary);
|
||||
filter: drop-shadow(0 0 5px var(--accent-glow));
|
||||
}
|
||||
|
||||
/* Loading Section */
|
||||
.loading-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
animation: fadeIn 0.5s ease;
|
||||
}
|
||||
|
||||
.loader-circle {
|
||||
position: relative;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
svg {
|
||||
transform: rotate(-90deg);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
circle {
|
||||
fill: none;
|
||||
stroke-width: 6;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.bg {
|
||||
stroke: var(--card-bg);
|
||||
}
|
||||
|
||||
.fg {
|
||||
stroke: var(--accent-color);
|
||||
stroke-dasharray: 283;
|
||||
stroke-dashoffset: 283;
|
||||
transition: stroke-dashoffset 0.5s ease;
|
||||
}
|
||||
|
||||
.loader-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 3rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.terminal-log {
|
||||
margin-top: 2rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
font-family: 'Consolas', monospace;
|
||||
text-align: left;
|
||||
height: 150px;
|
||||
overflow-y: auto;
|
||||
font-size: 0.9rem;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.log-line {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
/* Result Section */
|
||||
.result-section {
|
||||
animation: slideUp 0.5s ease;
|
||||
}
|
||||
|
||||
.success-header {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.checkmark-circle {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 1.5rem;
|
||||
color: #10b981;
|
||||
font-size: 3rem;
|
||||
box-shadow: 0 0 30px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.verification-container {
|
||||
background: var(--card-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.comparison-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
width: 250px;
|
||||
height: 150px;
|
||||
/* fixed aspect ratio roughly */
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.image-wrapper img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
/* or contain */
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.img-label {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
color: white;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.lg-accent {
|
||||
color: var(--accent-color);
|
||||
border: 1px solid var(--accent-color);
|
||||
}
|
||||
|
||||
.vs-badge {
|
||||
background: var(--bg-darker);
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
font-weight: bold;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.actions-grid {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary,
|
||||
.btn-secondary {
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-size: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--accent-color), var(--secondary-color));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
margin-top: 2rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-text:hover {
|
||||
color: var(--text-primary);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.comparison-card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions-grid {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
/* Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.header {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.logo i {
|
||||
font-size: 2rem;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.logo h1 {
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 20px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.status-indicator.online i {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
.status-indicator.offline i {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.status-indicator.checking i {
|
||||
color: #ffc107;
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main {
|
||||
padding: 2rem 0;
|
||||
min-height: calc(100vh - 140px);
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
text-align: center;
|
||||
margin-bottom: 3rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.hero h2 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Cards */
|
||||
.card {
|
||||
background: white;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
margin-bottom: 2rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 1.5rem;
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Forms */
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-group i {
|
||||
position: absolute;
|
||||
left: 1rem;
|
||||
color: #999;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.input-group input {
|
||||
width: 100%;
|
||||
padding: 1rem 1rem 1rem 3rem;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
transition: all 0.3s ease;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.input-group input:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
background: white;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 25px rgba(40, 167, 69, 0.3);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #667eea;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 1rem;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* Stats Grid */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-card .stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.stat-card .stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #666;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Tabs */
|
||||
.results-tabs {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
display: flex;
|
||||
border-bottom: 2px solid #e9ecef;
|
||||
margin-bottom: 1.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
background: none;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
border-bottom: 3px solid transparent;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: #667eea;
|
||||
border-bottom-color: #667eea;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-pane.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Overview Content */
|
||||
.overview-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.overview-item .label {
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.overview-item .value {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Links List */
|
||||
.link-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.link-item i {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.link-item a {
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
flex: 1;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.link-item a:hover {
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
/* Images Grid */
|
||||
.images-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.image-item img {
|
||||
max-width: 100%;
|
||||
height: 120px;
|
||||
object-fit: cover;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.image-item .image-url {
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
/* JSON Output */
|
||||
.json-output {
|
||||
background: #2d3748;
|
||||
color: #e2e8f0;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Error Card */
|
||||
.error-card {
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
|
||||
.error-card .card-body {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-card i {
|
||||
font-size: 3rem;
|
||||
color: #dc3545;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-card h3 {
|
||||
color: #dc3545;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.2);
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Utilities */
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.hero h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tab-buttons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,288 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const cors = require('cors');
|
||||
const axios = require('axios');
|
||||
const { exec, spawn } = require('child_process');
|
||||
|
||||
const app = express();
|
||||
const PORT = 4000;
|
||||
|
||||
// Security headers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('X-XSS-Protection', '1; mode=block');
|
||||
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
||||
next();
|
||||
});
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Serve static files with proper MIME types
|
||||
app.use(express.static(path.join(__dirname, 'public'), {
|
||||
setHeaders: (res, path) => {
|
||||
if (path.endsWith('.css')) res.setHeader('Content-Type', 'text/css');
|
||||
if (path.endsWith('.js')) res.setHeader('Content-Type', 'application/javascript');
|
||||
if (path.endsWith('.html')) res.setHeader('Content-Type', 'text/html');
|
||||
}
|
||||
}));
|
||||
|
||||
// Serve cloned sites directory
|
||||
app.use(express.static(path.join(__dirname, '..', 'cloned-sites'), {
|
||||
setHeaders: (res, filePath) => {
|
||||
if (filePath.endsWith('.css')) res.setHeader('Content-Type', 'text/css');
|
||||
if (filePath.endsWith('.js')) res.setHeader('Content-Type', 'application/javascript');
|
||||
}
|
||||
}));
|
||||
|
||||
// Proxy para o crawler service - SEM TIMEOUT
|
||||
app.post('/api/crawl', async (req, res) => {
|
||||
try {
|
||||
console.log('🚀 Proxying crawl request to crawler service...');
|
||||
console.log('⏳ Clonagem pode demorar vários minutos para sites grandes...');
|
||||
|
||||
// SEM TIMEOUT - permite clonagens longas
|
||||
const response = await axios.post('http://localhost:3001/crawl', req.body, {
|
||||
timeout: 0 // Sem limite de tempo
|
||||
});
|
||||
|
||||
console.log('✅ Crawl completed successfully');
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('❌ Crawl error:', error.message);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.response?.data?.error || error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Proxy para o wget cloner - SEM TIMEOUT para clonagens grandes
|
||||
app.post('/api/wget-clone', async (req, res) => {
|
||||
try {
|
||||
console.log('🔧 Proxying wget clone request...');
|
||||
console.log('⏳ Clonagem pode demorar vários minutos para sites grandes...');
|
||||
|
||||
// SEM TIMEOUT - permite clonagens que demoram mais de 10 minutos
|
||||
const response = await axios.post('http://localhost:3002/wget-clone', req.body, {
|
||||
timeout: 0 // Sem limite de tempo
|
||||
});
|
||||
|
||||
console.log('✅ Wget clone completed successfully');
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
console.error('❌ Wget clone error:', error.message);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.response?.data?.error || error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Smart Clone Endpoint (Local Spawn)
|
||||
app.post('/api/smart-clone', (req, res) => {
|
||||
const { url } = req.body;
|
||||
|
||||
if (!url) {
|
||||
return res.status(400).json({ error: 'URL is required' });
|
||||
}
|
||||
|
||||
console.log('🚀 Starting Smart Clone for:', url);
|
||||
console.log('⏳ This uses Puppeteer and may take a while...');
|
||||
|
||||
// Spawn the smart cloner process
|
||||
const scriptPath = path.join(__dirname, '..', 'simple-crawler', 'smart-cloner.js');
|
||||
console.log(`[SmartClone] Executing: node "${scriptPath}" --url="${url}"`);
|
||||
|
||||
const child = spawn('node', [scriptPath, `--url=${url}`], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
shell: true
|
||||
});
|
||||
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
output += text;
|
||||
console.log(`[SmartClone STDOUT] ${text.trim()}`);
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
const text = data.toString();
|
||||
errorOutput += text;
|
||||
console.error(`[SmartClone STDERR] ${text.trim()}`);
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
console.error('[SmartClone SPAWN ERROR]', err);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ success: false, error: 'Failed to start cloning process', details: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
console.log(`[SmartClone] Process exited with code ${code}`);
|
||||
|
||||
if (res.headersSent) return;
|
||||
|
||||
try {
|
||||
// Find the last JSON object in output
|
||||
const matches = output.match(/\{[\s\S]*\}/g);
|
||||
if (matches && matches.length > 0) {
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
const result = JSON.parse(lastMatch);
|
||||
res.json({
|
||||
success: result.success,
|
||||
data: result,
|
||||
error: result.error
|
||||
});
|
||||
} else {
|
||||
throw new Error('No valid JSON output found from cloner');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[SmartClone PARSE ERROR]', e.message);
|
||||
if (code === 0) {
|
||||
res.json({ success: true, message: 'Process finished but output parsing failed', fullOutput: output });
|
||||
} else {
|
||||
res.status(500).json({ success: false, error: 'Clone failed or output was invalid', details: errorOutput });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Health check do wget cloner (porta 3002)
|
||||
app.get('/api/health', async (req, res) => {
|
||||
try {
|
||||
const response = await axios.get('http://localhost:3002/health', {
|
||||
timeout: 5000
|
||||
});
|
||||
res.json(response.data);
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'WGET Cloner service unavailable'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Abrir pasta do clone
|
||||
app.post('/api/open-folder', (req, res) => {
|
||||
try {
|
||||
const { path: folderPath } = req.body;
|
||||
if (!folderPath) {
|
||||
return res.status(400).json({ error: 'Caminho da pasta é obrigatório' });
|
||||
}
|
||||
|
||||
// Abrir pasta no Windows Explorer
|
||||
exec(`explorer "${folderPath}"`, (error) => {
|
||||
if (error) {
|
||||
console.error('Erro ao abrir pasta:', error);
|
||||
return res.status(500).json({ error: 'Não foi possível abrir a pasta' });
|
||||
}
|
||||
res.json({ success: true, message: 'Pasta aberta com sucesso' });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao abrir pasta:', error);
|
||||
res.status(500).json({ error: 'Erro interno do servidor' });
|
||||
}
|
||||
});
|
||||
|
||||
// Abrir site clonado via HTTP para evitar problemas de origem/CORS
|
||||
app.post('/api/open-site', (req, res) => {
|
||||
try {
|
||||
const { path: sitePath } = req.body;
|
||||
if (!sitePath) {
|
||||
return res.status(400).json({ error: 'Caminho do site é obrigatório' });
|
||||
}
|
||||
|
||||
// Extrair apenas o nome da pasta (último segmento do caminho)
|
||||
const folderName = path.basename(sitePath);
|
||||
const siteUrl = `http://localhost:${PORT}/${folderName}/index.html`;
|
||||
|
||||
console.log(`🌍 Opening site via HTTP: ${siteUrl}`);
|
||||
|
||||
// Abrir URL no navegador padrão
|
||||
exec(`start "" "${siteUrl}"`, (error) => {
|
||||
if (error) {
|
||||
console.error('Erro ao abrir site:', error);
|
||||
return res.status(500).json({ error: 'Não foi possível abrir o site via HTTP' });
|
||||
}
|
||||
res.json({ success: true, message: 'Site aberto via HTTP com sucesso', url: siteUrl });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao abrir site:', error);
|
||||
res.status(500).json({ error: 'Erro interno do servidor' });
|
||||
}
|
||||
});
|
||||
|
||||
// Abrir página de navegação via HTTP
|
||||
app.post('/api/open-navigation', (req, res) => {
|
||||
try {
|
||||
const { path: sitePath } = req.body;
|
||||
if (!sitePath) {
|
||||
return res.status(400).json({ error: 'Caminho do site é obrigatório' });
|
||||
}
|
||||
|
||||
const folderName = path.basename(sitePath);
|
||||
const navUrl = `http://localhost:${PORT}/${folderName}/navegacao.html`;
|
||||
|
||||
console.log(`🌍 Opening navigation via HTTP: ${navUrl}`);
|
||||
|
||||
// Abrir navegacao.html no navegador padrão
|
||||
exec(`start "" "${navUrl}"`, (error) => {
|
||||
if (error) {
|
||||
console.error('Erro ao abrir navegação:', error);
|
||||
return res.status(500).json({ error: 'Não foi possível abrir a navegação via HTTP' });
|
||||
}
|
||||
res.json({ success: true, message: 'Navegação aberta via HTTP com sucesso', url: navUrl });
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erro ao abrir navegação:', error);
|
||||
res.status(500).json({ error: 'Erro interno do servidor' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index-premium.html'));
|
||||
});
|
||||
|
||||
// Serve old interface
|
||||
app.get('/old', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
});
|
||||
|
||||
// Serve favicon requests
|
||||
app.get('/favicon.ico', (req, res) => {
|
||||
res.redirect('/favicon.svg');
|
||||
});
|
||||
|
||||
// Handle common missing resources
|
||||
app.get('/index', (req, res) => {
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// Handle 404s
|
||||
app.use((req, res) => {
|
||||
res.status(404).json({
|
||||
error: 'Not Found',
|
||||
message: `Route ${req.path} not found`
|
||||
});
|
||||
});
|
||||
|
||||
// Error handler
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('Server error:', err);
|
||||
res.status(500).json({
|
||||
error: 'Internal Server Error',
|
||||
message: err.message
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🌐 CloneWeb Interface running on http://localhost:${PORT}`);
|
||||
console.log(`🔗 Make sure crawler service is running on http://localhost:3001`);
|
||||
console.log(`📁 Serving static files from: ${path.join(__dirname, 'public')}`);
|
||||
});
|
||||
Reference in New Issue
Block a user