PRIMEIRO ENVIO

This commit is contained in:
2025-12-25 12:02:07 -03:00
parent ca49575224
commit c4d3bd9a86
92 changed files with 26976 additions and 1 deletions
+961
View File
@@ -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"
}
}
}
}
+16
View File
@@ -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"
}
}
+692
View File
@@ -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' : `${days} dias`;
if (hours > 0) return hours === 1 ? 'Há 1 hora' : `${hours} horas`;
if (minutes > 0) return minutes === 1 ? 'Há 1 minuto' : `${minutes} minutos`;
return 'Agora mesmo';
}
+160
View File
@@ -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);
}
+452
View File
@@ -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');
});
}
+19
View File
@@ -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;
}
View File
+4
View File
@@ -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

+297
View File
@@ -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&#10;twitter.com&#10;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>
+166
View File
@@ -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>&copy; 2025 CloneWeb Platform. Desenvolvido com ❤️ para crawling inteligente.</p>
</div>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>
+123
View File
@@ -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>
+740
View File
@@ -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;
}
+502
View File
@@ -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;
}
}
+477
View File
@@ -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;
}
}
+288
View File
@@ -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')}`);
});