diff --git a/.gitignore b/.gitignore index 32e06969e..7ab221f97 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,10 @@ .DS_Store *.pem +# log files +error.log +homepage.log + # debug npm-debug.log* yarn-debug.log* diff --git a/package.json b/package.json index dd9f7f87a..bfc67e6fe 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "rutorrent-promise": "^2.0.0", "shvl": "^3.0.0", "swr": "^1.3.0", - "tough-cookie": "^4.1.2" + "tough-cookie": "^4.1.2", + "winston": "^3.8.2" }, "devDependencies": { "autoprefixer": "^10.4.9", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 193092180..9d0f65b07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,7 @@ specifiers: tailwindcss: ^3.1.8 tough-cookie: ^4.1.2 typescript: ^4.8.3 + winston: ^3.8.2 dependencies: '@headlessui/react': 1.7.0_biqbaboplfbrettd7655fr4n2y @@ -63,6 +64,7 @@ dependencies: shvl: 3.0.0 swr: 1.3.0_react@18.2.0 tough-cookie: 4.1.2 + winston: 3.8.2 devDependencies: autoprefixer: 10.4.9_postcss@8.4.16 @@ -100,6 +102,19 @@ packages: resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==} dev: false + /@colors/colors/1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + dev: false + + /@dabh/diagnostics/2.0.3: + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + dev: false + /@eslint/eslintrc/1.3.2: resolution: {integrity: sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -500,6 +515,10 @@ packages: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: true + /async/3.2.4: + resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} + dev: false + /asynckit/0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: false @@ -646,6 +665,12 @@ packages: resolution: {integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==} dev: false + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: false + /color-convert/2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -653,9 +678,34 @@ packages: color-name: 1.1.4 dev: true + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: false + /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-string/1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + + /color/3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + dev: false + + /colorspace/1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + dev: false + /combined-stream/1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -837,6 +887,10 @@ packages: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true + /enabled/2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + dev: false + /end-of-stream/1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: @@ -1323,6 +1377,10 @@ packages: dependencies: reusify: 1.0.4 + /fecha/4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + dev: false + /file-entry-cache/6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1356,6 +1414,10 @@ packages: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} dev: true + /fn.name/1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + dev: false + /follow-redirects/1.15.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -1602,6 +1664,10 @@ packages: side-channel: 1.0.4 dev: true + /is-arrayish/0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + /is-bigint/1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} dependencies: @@ -1679,6 +1745,11 @@ packages: call-bind: 1.0.2 dev: true + /is-stream/2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: false + /is-string/1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -1743,6 +1814,10 @@ packages: object.assign: 4.1.4 dev: true + /kuler/2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + dev: false + /language-subtag-registry/0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -1776,6 +1851,16 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /logform/2.4.2: + resolution: {integrity: sha512-W4c9himeAwXEdZ05dQNerhFz2XG80P9Oj0loPUMV23VC2it0orMHQhJm4hdnnor3rd1HsGf6a2lPwBM1zeXHGw==} + dependencies: + '@colors/colors': 1.5.0 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.0 + triple-beam: 1.3.0 + dev: false + /loose-envify/1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1843,7 +1928,6 @@ packages: /ms/2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true /nan/2.16.0: resolution: {integrity: sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==} @@ -2000,6 +2084,12 @@ packages: dependencies: wrappy: 1.0.2 + /one-time/1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + dependencies: + fn.name: 1.1.0 + dev: false + /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -2347,6 +2437,11 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false + /safe-stable-stringify/2.4.0: + resolution: {integrity: sha512-eehKHKpab6E741ud7ZIMcXhKcP6TSIezPkNZhy5U8xC6+VvrRdUA2tMgxGxaGl4cz7c2Ew5+mg5+wNB16KQqrA==} + engines: {node: '>=10'} + dev: false + /safer-buffer/2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: false @@ -2398,6 +2493,12 @@ packages: object-inspect: 1.12.2 dev: true + /simple-swizzle/0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + /slash/3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -2423,6 +2524,10 @@ packages: nan: 2.16.0 dev: false + /stack-trace/0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + dev: false + /statuses/2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -2567,6 +2672,10 @@ packages: readable-stream: 3.6.0 dev: false + /text-hex/1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + dev: false + /text-table/0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -2596,6 +2705,10 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false + /triple-beam/1.3.0: + resolution: {integrity: sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==} + dev: false + /tsconfig-paths/3.14.1: resolution: {integrity: sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==} dependencies: @@ -2733,6 +2846,32 @@ packages: isexe: 2.0.0 dev: true + /winston-transport/4.5.0: + resolution: {integrity: sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==} + engines: {node: '>= 6.4.0'} + dependencies: + logform: 2.4.2 + readable-stream: 3.6.0 + triple-beam: 1.3.0 + dev: false + + /winston/3.8.2: + resolution: {integrity: sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==} + engines: {node: '>= 12.0.0'} + dependencies: + '@colors/colors': 1.5.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.4 + is-stream: 2.0.1 + logform: 2.4.2 + one-time: 1.0.0 + readable-stream: 3.6.0 + safe-stable-stringify: 2.4.0 + stack-trace: 0.0.10 + triple-beam: 1.3.0 + winston-transport: 4.5.0 + dev: false + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} diff --git a/public/locales/ca/common.json b/public/locales/ca/common.json index a07636336..115722ad9 100644 --- a/public/locales/ca/common.json +++ b/public/locales/ca/common.json @@ -155,7 +155,12 @@ "qbittorrent": { "download": "Descàrrega", "upload": "Càrrega", - "leech": "Leech", - "seed": "Seed" + "leech": "Companys", + "seed": "Llavors" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/de/common.json b/public/locales/de/common.json index f5b99bc94..b3c180800 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -157,5 +157,10 @@ "upload": "Hochladen", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index cb9c20c5b..4641a63b8 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -174,5 +174,10 @@ "numConnections": "Connections", "dataRelayed": "Relayed", "transferRate": "Rate" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/es/common.json b/public/locales/es/common.json index d5d32ce0f..3c3e84091 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -157,5 +157,10 @@ "upload": "Subida", "leech": "Compañeros", "seed": "Semillas" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index 14544d2d4..39ce2988a 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -168,5 +168,10 @@ "upload": "Envoi", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/he/common.json b/public/locales/he/common.json index fcd4e2cbb..0db4b03a6 100644 --- a/public/locales/he/common.json +++ b/public/locales/he/common.json @@ -157,5 +157,10 @@ "jackett": { "configured": "מוגדר", "errored": "שגיאה" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/hr/common.json b/public/locales/hr/common.json index a2d66c817..9cbeaee25 100644 --- a/public/locales/hr/common.json +++ b/public/locales/hr/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json index a3a3abb41..3cbc7b5a5 100644 --- a/public/locales/hu/common.json +++ b/public/locales/hu/common.json @@ -157,5 +157,10 @@ "jackett": { "configured": "Beállított", "errored": "Hibás" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/it/common.json b/public/locales/it/common.json index 2e07b8f9f..09241cefb 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -157,5 +157,10 @@ "leech": "Leech", "upload": "Upload", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/nb-NO/common.json b/public/locales/nb-NO/common.json index 93bd9c1c8..1ee92bf40 100644 --- a/public/locales/nb-NO/common.json +++ b/public/locales/nb-NO/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json index d439e2a75..f52a07aba 100644 --- a/public/locales/nl/common.json +++ b/public/locales/nl/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json index 16e53894c..d23b4be0d 100644 --- a/public/locales/pl/common.json +++ b/public/locales/pl/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 6394df8d5..2ba75ec1b 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -168,5 +168,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 73da2e366..f63496bd9 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json index 4e52f59dc..da0e60d42 100644 --- a/public/locales/sv/common.json +++ b/public/locales/sv/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index e72c01a6c..2fdbd010f 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json index f8d9f802e..6f941905d 100644 --- a/public/locales/zh-CN/common.json +++ b/public/locales/zh-CN/common.json @@ -157,5 +157,10 @@ "upload": "上传", "leech": "吸血", "seed": "做种" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/public/locales/zh-Hant/common.json b/public/locales/zh-Hant/common.json index 6dd8c9b2b..46cbefcb0 100644 --- a/public/locales/zh-Hant/common.json +++ b/public/locales/zh-Hant/common.json @@ -157,5 +157,10 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" } } diff --git a/src/components/bookmarks/group.jsx b/src/components/bookmarks/group.jsx index bf61bcd92..af5100816 100644 --- a/src/components/bookmarks/group.jsx +++ b/src/components/bookmarks/group.jsx @@ -2,13 +2,8 @@ import List from "components/bookmarks/list"; export default function BookmarksGroup({ group }) { return ( -
-

- {group.name} -

+
+

{group.name}

); diff --git a/src/components/bookmarks/item.jsx b/src/components/bookmarks/item.jsx index 5f4e53897..c3c5b452f 100644 --- a/src/components/bookmarks/item.jsx +++ b/src/components/bookmarks/item.jsx @@ -1,12 +1,18 @@ +import { useContext } from "react"; + +import { SettingsContext } from "utils/settings-context"; + export default function Item({ bookmark }) { const { hostname } = new URL(bookmark.href); + const { settings } = useContext(SettingsContext); return (
  • - +
  • ); } diff --git a/src/components/services/item.jsx b/src/components/services/item.jsx index df570fcae..82f3741c8 100644 --- a/src/components/services/item.jsx +++ b/src/components/services/item.jsx @@ -1,10 +1,13 @@ import Image from "next/future/image"; +import { useContext } from "react"; import { Disclosure } from "@headlessui/react"; import Status from "./status"; import Widget from "./widget"; import Docker from "./widgets/service/docker"; +import { SettingsContext } from "utils/settings-context"; + function resolveIcon(icon) { if (icon.startsWith("http")) { return `/api/proxy?url=${encodeURIComponent(icon)}`; @@ -23,6 +26,7 @@ function resolveIcon(icon) { export default function Item({ service }) { const hasLink = service.href && service.href !== "#"; + const { settings } = useContext(SettingsContext); return (
  • @@ -37,7 +41,7 @@ export default function Item({ service }) { (hasLink ? ( @@ -52,7 +56,7 @@ export default function Item({ service }) { {hasLink ? ( diff --git a/src/components/services/widget.jsx b/src/components/services/widget.jsx index c3fb16305..e9bb394db 100644 --- a/src/components/services/widget.jsx +++ b/src/components/services/widget.jsx @@ -28,6 +28,7 @@ import Prowlarr from "./widgets/service/prowlarr"; import Jackett from "./widgets/service/jackett"; import AdGuard from "./widgets/service/adguard"; import StRelaySrv from "./widgets/service/strelaysrv"; +import Mastodon from "./widgets/service/mastodon"; const widgetMappings = { docker: Docker, @@ -58,6 +59,7 @@ const widgetMappings = { jackett: Jackett, adguard: AdGuard, strelaysrv: StRelaySrv, + mastodon: Mastodon, }; export default function Widget({ service }) { diff --git a/src/components/services/widgets/service/mastodon.jsx b/src/components/services/widgets/service/mastodon.jsx new file mode 100644 index 000000000..9d2ded4a1 --- /dev/null +++ b/src/components/services/widgets/service/mastodon.jsx @@ -0,0 +1,37 @@ +import useSWR from "swr"; +import { useTranslation } from "react-i18next"; + +import Widget from "../widget"; +import Block from "../block"; + +import { formatApiUrl } from "utils/api-helpers"; + +export default function Mastodon({ service }) { + const { t } = useTranslation(); + + const config = service.widget; + + const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `instance`)); + + if (statsError) { + return ; + } + + if (!statsData) { + return ( + + + + + + ); + } + + return ( + + + + + + ); +} diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 32c9c23ff..cb9dd1a69 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -8,6 +8,7 @@ import "styles/theme.css"; import "utils/i18n"; import { ColorProvider } from "utils/color-context"; import { ThemeProvider } from "utils/theme-context"; +import { SettingsProvider } from "utils/settings-context"; function MyApp({ Component, pageProps }) { return ( @@ -18,7 +19,9 @@ function MyApp({ Component, pageProps }) { > - + + + diff --git a/src/pages/api/services/proxy.js b/src/pages/api/services/proxy.js index fa8b7ab36..1cc5a244d 100644 --- a/src/pages/api/services/proxy.js +++ b/src/pages/api/services/proxy.js @@ -1,3 +1,4 @@ +import logger from "utils/logger"; import genericProxyHandler from "utils/proxies/generic"; import credentialedProxyHandler from "utils/proxies/credentialed"; import rutorrentProxyHandler from "utils/proxies/rutorrent"; @@ -82,6 +83,7 @@ const serviceProxyHandlers = { jackett: genericProxyHandler, adguard: genericProxyHandler, strelaysrv: genericProxyHandler, + mastodon: genericProxyHandler, // uses X-API-Key (or similar) header auth gotify: credentialedProxyHandler, portainer: credentialedProxyHandler, @@ -99,20 +101,27 @@ const serviceProxyHandlers = { }; export default async function handler(req, res) { - const { type } = req.query; + try { + const { type } = req.query; - const serviceProxyHandler = serviceProxyHandlers[type]; + const serviceProxyHandler = serviceProxyHandlers[type]; - if (serviceProxyHandler) { - if (serviceProxyHandler instanceof Function) { - return serviceProxyHandler(req, res); + if (serviceProxyHandler) { + if (serviceProxyHandler instanceof Function) { + return serviceProxyHandler(req, res); + } + + const { proxy, maps } = serviceProxyHandler; + if (proxy) { + return proxy(req, res, maps); + } } - const { proxy, maps } = serviceProxyHandler; - if (proxy) { - return proxy(req, res, maps); - } + logger.debug("Unknown proxy service type: %s", type); + return res.status(403).json({ error: "Unkown proxy service type" }); + } + catch (ex) { + logger.error(ex); + return res.status(500).send({ error: "Unexpected error" }); } - - return res.status(403).json({ error: "Unkown proxy service type" }); } diff --git a/src/pages/index.jsx b/src/pages/index.jsx index 1b20a020b..207b2e6ac 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -13,6 +13,7 @@ import Revalidate from "components/revalidate"; import { getSettings } from "utils/config"; import { ColorContext } from "utils/color-context"; import { ThemeContext } from "utils/theme-context"; +import { SettingsContext } from "utils/settings-context"; const ThemeToggle = dynamic(() => import("components/theme-toggle"), { ssr: false, @@ -26,22 +27,23 @@ const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "search" export function getStaticProps() { try { - const settings = getSettings(); + const { providers, ...settings } = getSettings(); + return { props: { - settings, + initialSettings: settings, }, }; } catch (e) { return { props: { - settings: {}, + initialSettings: {}, }, }; } } -export default function Index({ settings }) { +export default function Index({ initialSettings }) { const { data: errorsData } = useSWR("/api/validate"); if (errorsData && errorsData.length > 0) { @@ -68,20 +70,25 @@ export default function Index({ settings }) { ); } - return ; + return ; } -function Home({ settings }) { +function Home({ initialSettings }) { const { i18n } = useTranslation(); const { theme, setTheme } = useContext(ThemeContext); const { color, setColor } = useContext(ColorContext); + const { settings, setSettings } = useContext(SettingsContext); + + if (initialSettings) { + setSettings(initialSettings); + } const { data: services } = useSWR("/api/services"); const { data: bookmarks } = useSWR("/api/bookmarks"); const { data: widgets } = useSWR("/api/widgets"); const wrappedStyle = {}; - if (settings.background) { + if (settings && settings.background) { wrappedStyle.backgroundImage = `url(${settings.background})`; wrappedStyle.backgroundSize = "cover"; wrappedStyle.opacity = settings.backgroundOpacity ?? 1; diff --git a/src/utils/api-helpers.js b/src/utils/api-helpers.js index 464907818..4b5b72d18 100644 --- a/src/utils/api-helpers.js +++ b/src/utils/api-helpers.js @@ -25,6 +25,7 @@ const formats = { jackett: `{url}/api/v2.0/{endpoint}?apikey={key}&configured=true`, adguard: `{url}/control/{endpoint}`, strelaysrv: `{url}/{endpoint}`, + mastodon: `{url}/api/v1/{endpoint}`, }; export function formatApiCall(api, args) { diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 000000000..2c2fea660 --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,76 @@ +import winston from "winston"; + +function messageFormatter(logInfo) { + if (logInfo.stack) { + return `[${logInfo.timestamp}] ${logInfo.level}: ${logInfo.stack}`; + } + return `[${logInfo.timestamp}] ${logInfo.level}: ${logInfo.message}`; +}; + +const consoleFormat = winston.format.combine( + winston.format.errors({ stack: true }), + winston.format.splat(), + winston.format.timestamp(), + winston.format.colorize(), + winston.format.printf(messageFormatter) +); + +const fileFormat = winston.format.combine( + winston.format.errors({ stack: true }), + winston.format.splat(), + winston.format.timestamp(), + winston.format.printf(messageFormatter) +); + +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + transports: [ + new winston.transports.Console({ + format: consoleFormat, + handleExceptions: true, + handleRejections: true + }), + + new winston.transports.File({ + format: fileFormat, + filename: 'homepage.log', + handleExceptions: true, + handleRejections: true + }), + ] +}); + +function debug(message, ...args) { + logger.debug(message, ...args); +} + +function verbose(message, ...args) { + logger.verbose(message, ...args); +} + +function info(message, ...args) { + logger.info(message, ...args); +} + +function warn(message, ...args) { + logger.warn(message, ...args); +} + +function error(message, ...args) { + logger.error(message, ...args); +} + +function crit(message, ...args) { + logger.crit(message, ...args); +} + +const thisModule = { + debug, + verbose, + info, + warn, + error, + crit +}; + +export default thisModule; \ No newline at end of file diff --git a/src/utils/proxies/generic.js b/src/utils/proxies/generic.js index 41c013dbb..06c6bbf46 100644 --- a/src/utils/proxies/generic.js +++ b/src/utils/proxies/generic.js @@ -1,6 +1,7 @@ import getServiceWidget from "utils/service-helpers"; import { formatApiCall } from "utils/api-helpers"; import { httpProxy } from "utils/http"; +import logger from "utils/logger"; export default async function genericProxyHandler(req, res, maps) { const { group, service, endpoint } = req.query; @@ -24,7 +25,7 @@ export default async function genericProxyHandler(req, res, maps) { }); let resultData = data; - if (maps?.[endpoint]) { + if ((status === 200) && (maps?.[endpoint])) { resultData = maps[endpoint](data); } @@ -34,9 +35,14 @@ export default async function genericProxyHandler(req, res, maps) { return res.status(status).end(); } + if (status >= 400) { + logger.debug("HTTP Error %d calling %s//%s%s...", status, url.protocol, url.hostname, url.pathname); + } + return res.status(status).send(resultData); } } + logger.debug("Invalid or missing proxy service type '%s' in group '%s'", service, group); return res.status(400).json({ error: "Invalid proxy service type" }); } diff --git a/src/utils/settings-context.jsx b/src/utils/settings-context.jsx new file mode 100644 index 000000000..d6993b14b --- /dev/null +++ b/src/utils/settings-context.jsx @@ -0,0 +1,15 @@ +import { createContext, useState, useMemo } from "react"; + +export const SettingsContext = createContext(); + +export function SettingsProvider({ initialSettings, children }) { + const [settings, setSettings] = useState({}); + + if (initialSettings) { + setSettings(initialSettings); + } + + const value = useMemo(() => ({ settings, setSettings }), [settings]); + + return {children}; +}