diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..3e283d69 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: benphelps +ko_fi: benphelps +custom: ["https://paypal.me/phelpsben"] diff --git a/.gitignore b/.gitignore index 32e06969..7ab221f9 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/README.md b/README.md index ed7c5e6f..d7f7f08a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ ![Homepage Preview](/images/preview.png) -[![Docker](https://github.com/benphelps/homepage/actions/workflows/docker-publish.yml/badge.svg)](https://github.com/benphelps/homepage/actions/workflows/docker-publish.yml) -[![Weblate](https://hosted.weblate.org/widgets/homepage/-/homepage/svg-badge.svg)](https://hosted.weblate.org/engage/homepage/) +

+ + Linkedin - phelpsben +

+ +

+Docker +Weblate +

+ ## Features @@ -18,9 +26,9 @@ - Service Integration - Sonarr, Radarr, Readarr, Prowlarr, Bazarr, Lidarr, Emby, Jellyfin, Tautulli (Plex) - Ombi, Overseerr, Jellyseerr, Jackett, NZBGet, SABnzbd, ruTorrent, Transmission, qBittorrent - - Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify + - Portainer, Traefik, Speedtest Tracker, PiHole, AdGuard Home, Nginx Proxy Manager, Gotify, Syncthing Relay Server - Information Providers - - Coin Market Cap + - Coin Market Cap, Mastodon - Information & Utility Widgets - System Stats (Disk, CPU, Memory) - Weather via WeatherAPI.com or OpenWeatherMap @@ -123,9 +131,10 @@ Huge thanks to the all the contributors who have helped make this project what i - [andrii-kryvoviaz](https://github.com/benphelps/homepage/commits?author=andrii-kryvoviaz) - Background opacity option - [boerniee](https://github.com/benphelps/homepage/commits?author=boerniee) - German Translation - [comradekingu](https://github.com/benphelps/homepage/commits?author=comradekingu) - Norwegian Bokmål Translation -- [Daniel Varga] - German & Hungarian Translation +- Daniel Varga - German & Hungarian Translation - [deffcolony](https://github.com/benphelps/homepage/commits?author=deffcolony) - Dutch Translation - [desolaris](https://github.com/benphelps/homepage/commits?author=desolaris) - Russian Translation +- [DevPGSV](https://github.com/benphelps/homepage/commits?author=DevPGSV) - Syncthing Relay Server & Mastodon widgets - [ilusi0n](https://github.com/benphelps/homepage/commits?author=ilusi0n) - Jellyseerr Integration - [ItsJustMeChris](https://github.com/benphelps/homepage/commits?author=ItsJustMeChris) - Coin Market Cap Widget - [jackblk](https://github.com/benphelps/homepage/commits?author=jackblk) - Vietnamese Translation diff --git a/package.json b/package.json index dd9f7f87..cf9c4131 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "rutorrent-promise": "^2.0.0", "shvl": "^3.0.0", "swr": "^1.3.0", - "tough-cookie": "^4.1.2" + "tailwind-scrollbar": "^2.0.1", + "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 19309218..e2625a90 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,9 +35,11 @@ specifiers: rutorrent-promise: ^2.0.0 shvl: ^3.0.0 swr: ^1.3.0 + tailwind-scrollbar: ^2.0.1 tailwindcss: ^3.1.8 tough-cookie: ^4.1.2 typescript: ^4.8.3 + winston: ^3.8.2 dependencies: '@headlessui/react': 1.7.0_biqbaboplfbrettd7655fr4n2y @@ -62,7 +64,9 @@ dependencies: rutorrent-promise: 2.0.0 shvl: 3.0.0 swr: 1.3.0_react@18.2.0 + tailwind-scrollbar: 2.0.1_tailwindcss@3.1.8 tough-cookie: 4.1.2 + winston: 3.8.2 devDependencies: autoprefixer: 10.4.9_postcss@8.4.16 @@ -100,6 +104,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 +517,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 +667,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 +680,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 +889,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 +1379,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 +1416,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 +1666,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 +1747,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 +1816,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 +1853,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 +1930,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 +2086,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 +2439,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 +2495,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 +2526,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'} @@ -2515,6 +2622,15 @@ packages: react: 18.2.0 dev: false + /tailwind-scrollbar/2.0.1_tailwindcss@3.1.8: + resolution: {integrity: sha512-OcR7qHBbux4k+k6bWqnEQFYFooLK/F4dhkBz6nvswIoaA9ancZ5h20e0tyV7ifSWLDCUBtpG+1NHRA8HMRH/wg==} + engines: {node: '>=12.13.0'} + peerDependencies: + tailwindcss: 3.x + dependencies: + tailwindcss: 3.1.8_postcss@8.4.16 + dev: false + /tailwindcss/3.1.8_postcss@8.4.16: resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==} engines: {node: '>=12.13.0'} @@ -2567,6 +2683,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 +2716,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 +2857,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 a0763633..e9ba3641 100644 --- a/public/locales/ca/common.json +++ b/public/locales/ca/common.json @@ -155,7 +155,18 @@ "qbittorrent": { "download": "Descàrrega", "upload": "Càrrega", - "leech": "Leech", - "seed": "Seed" + "leech": "Companys", + "seed": "Llavors" + }, + "mastodon": { + "user_count": "Usuaris", + "status_count": "Publicacions", + "domain_count": "Dominis" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connexions", + "dataRelayed": "Transmès", + "transferRate": "Velocitat" } } diff --git a/public/locales/de/common.json b/public/locales/de/common.json index 08ddddd7..cff97b11 100644 --- a/public/locales/de/common.json +++ b/public/locales/de/common.json @@ -48,9 +48,9 @@ "movies": "Filme" }, "readarr": { - "wanted": "Wanted", - "queued": "Queued", - "books": "Books" + "wanted": "Gesucht", + "queued": "In Warteschlange", + "books": "Bücher" }, "ombi": { "pending": "Ausstehend", @@ -79,8 +79,8 @@ }, "traefik": { "routers": "Router", - "services": "Services", - "middleware": "Middleware" + "services": "Dienste", + "middleware": "Zwischenanwendung" }, "npm": { "enabled": "Aktiviert", @@ -94,68 +94,79 @@ "wait": "Bitte warten" }, "overseerr": { - "pending": "Pending", - "approved": "Approved", - "available": "Available" + "pending": "Ausstehend", + "approved": "Genehmigt", + "available": "Verfügbar" }, "sabnzbd": { - "rate": "Rate", - "queue": "Queue", - "timeleft": "Time Left" + "rate": "Geschwindigkeit", + "queue": "Warteschlange", + "timeleft": "Verbleibende Zeit" }, "nzbget": { - "rate": "Rate", - "remaining": "Remaining", - "downloaded": "Downloaded" + "rate": "Geschwindigkeit", + "remaining": "Verbleibend", + "downloaded": "Heruntergeladen" }, "coinmarketcap": { - "configure": "Configure one or more crypto currencies to track", - "1hour": "1 Hour", - "1day": "1 Day", - "7days": "7 Days", - "30days": "30 Days" + "configure": "Konfiguriere eine oder mehrere Kryptowährungen zur Verfolgung", + "1hour": "1 Stunde", + "1day": "1 Tag", + "7days": "7 Tage", + "30days": "30 Tage" }, "gotify": { - "apps": "Applications", - "clients": "Clients", - "messages": "Messages" + "apps": "Programme", + "clients": "Benutzer", + "messages": "Nachrichten" }, "prowlarr": { - "enableIndexers": "Indexers", - "numberOfGrabs": "Grabs", - "numberOfQueries": "Queries", - "numberOfFailGrabs": "Fail Grabs", - "numberOfFailQueries": "Fail Queries" + "enableIndexers": "Indexer", + "numberOfGrabs": "Abrufungen", + "numberOfQueries": "Anfragen", + "numberOfFailGrabs": "Fehlgeschlagene Abrufungen", + "numberOfFailQueries": "Fehlgeschlagene Anfragen" }, "transmission": { - "download": "Download", - "upload": "Upload", + "download": "Herunterladen", + "upload": "Hochladen", "leech": "Leech", "seed": "Seed" }, "jackett": { - "configured": "Configured", - "errored": "Errored" + "configured": "Konfiguriert", + "errored": "Fehlerhaft" }, "bazarr": { - "missingEpisodes": "Missing Episodes", - "missingMovies": "Missing Movies" + "missingEpisodes": "Fehlende Episoden", + "missingMovies": "Fehlende Filme" }, "lidarr": { - "wanted": "Wanted", - "queued": "Queued", - "albums": "Albums" + "wanted": "Gesucht", + "queued": "In Warteschlange", + "albums": "Alben" }, "adguard": { - "queries": "Queries", - "blocked": "Blocked", - "filtered": "Filtered", - "latency": "Latency" + "queries": "Anfragen", + "blocked": "Blockiert", + "filtered": "Gefiltert", + "latency": "Latenz" }, "qbittorrent": { - "download": "Download", - "upload": "Upload", + "download": "Herunterladen", + "upload": "Hochladen", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/en/common.json b/public/locales/en/common.json index fb4de8c5..63776994 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -173,5 +173,16 @@ "users": "Users", "loginsLast24H": "Logins (24h)", "failedLoginsLast24H": "Failed Logins (24h)" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "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 d5d32ce0..47ffd142 100644 --- a/public/locales/es/common.json +++ b/public/locales/es/common.json @@ -157,5 +157,16 @@ "upload": "Subida", "leech": "Compañeros", "seed": "Semillas" + }, + "mastodon": { + "user_count": "Usuarios", + "status_count": "Publicaciones", + "domain_count": "Dominios" + }, + "strelaysrv": { + "numActiveSessions": "Sesiones", + "numConnections": "Conexiones", + "dataRelayed": "Transmitido", + "transferRate": "Velocidad" } } diff --git a/public/locales/fr/common.json b/public/locales/fr/common.json index 14544d2d..03d9c169 100644 --- a/public/locales/fr/common.json +++ b/public/locales/fr/common.json @@ -168,5 +168,16 @@ "upload": "Envoi", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Utilisateurs", + "status_count": "Messages", + "domain_count": "Domaines" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/he/common.json b/public/locales/he/common.json index fcd4e2cb..48a1acd5 100644 --- a/public/locales/he/common.json +++ b/public/locales/he/common.json @@ -157,5 +157,16 @@ "jackett": { "configured": "מוגדר", "errored": "שגיאה" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/hr/common.json b/public/locales/hr/common.json index a2d66c81..d1c69cdf 100644 --- a/public/locales/hr/common.json +++ b/public/locales/hr/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/hu/common.json b/public/locales/hu/common.json index a3a3abb4..e73ca38f 100644 --- a/public/locales/hu/common.json +++ b/public/locales/hu/common.json @@ -157,5 +157,16 @@ "jackett": { "configured": "Beállított", "errored": "Hibás" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/it/common.json b/public/locales/it/common.json index 2e07b8f9..2547fb34 100644 --- a/public/locales/it/common.json +++ b/public/locales/it/common.json @@ -157,5 +157,16 @@ "leech": "Leech", "upload": "Upload", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/nb-NO/common.json b/public/locales/nb-NO/common.json index 93bd9c1c..9d4924a7 100644 --- a/public/locales/nb-NO/common.json +++ b/public/locales/nb-NO/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/nl/common.json b/public/locales/nl/common.json index d439e2a7..c4eb9435 100644 --- a/public/locales/nl/common.json +++ b/public/locales/nl/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/pl/common.json b/public/locales/pl/common.json index 16e53894..9e51e98f 100644 --- a/public/locales/pl/common.json +++ b/public/locales/pl/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/pt/common.json b/public/locales/pt/common.json index 6394df8d..415eca6d 100644 --- a/public/locales/pt/common.json +++ b/public/locales/pt/common.json @@ -168,5 +168,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/ru/common.json b/public/locales/ru/common.json index 73da2e36..6cb0da89 100644 --- a/public/locales/ru/common.json +++ b/public/locales/ru/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate", + "numActiveSessions": "Sessions" } } diff --git a/public/locales/sv/common.json b/public/locales/sv/common.json index 4e52f59d..c87992dd 100644 --- a/public/locales/sv/common.json +++ b/public/locales/sv/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/vi/common.json b/public/locales/vi/common.json index e72c01a6..13dc48a8 100644 --- a/public/locales/vi/common.json +++ b/public/locales/vi/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/public/locales/zh-CN/common.json b/public/locales/zh-CN/common.json index f8d9f802..966755d7 100644 --- a/public/locales/zh-CN/common.json +++ b/public/locales/zh-CN/common.json @@ -157,5 +157,16 @@ "upload": "上传", "leech": "吸血", "seed": "做种" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "dataRelayed": "Relayed", + "numConnections": "Connections", + "transferRate": "Rate" } } diff --git a/public/locales/zh-Hant/common.json b/public/locales/zh-Hant/common.json index 6dd8c9b2..691d139c 100644 --- a/public/locales/zh-Hant/common.json +++ b/public/locales/zh-Hant/common.json @@ -157,5 +157,16 @@ "upload": "Upload", "leech": "Leech", "seed": "Seed" + }, + "mastodon": { + "user_count": "Users", + "status_count": "Posts", + "domain_count": "Domains" + }, + "strelaysrv": { + "numActiveSessions": "Sessions", + "numConnections": "Connections", + "dataRelayed": "Relayed", + "transferRate": "Rate" } } diff --git a/src/components/bookmarks/group.jsx b/src/components/bookmarks/group.jsx index bf61bcd9..af510081 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 5f4e5389..c3c5b452 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 df570fca..82f3741c 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 03794e72..d182e6ab 100644 --- a/src/components/services/widget.jsx +++ b/src/components/services/widget.jsx @@ -1,33 +1,36 @@ import { useTranslation } from "react-i18next"; +import dynamic from "next/dynamic"; -import Sonarr from "./widgets/service/sonarr"; -import Radarr from "./widgets/service/radarr"; -import Lidarr from "./widgets/service/lidarr"; -import Readarr from "./widgets/service/readarr"; -import Bazarr from "./widgets/service/bazarr"; -import Ombi from "./widgets/service/ombi"; -import Portainer from "./widgets/service/portainer"; -import Emby from "./widgets/service/emby"; -import Nzbget from "./widgets/service/nzbget"; -import SABnzbd from "./widgets/service/sabnzbd"; -import Transmission from "./widgets/service/transmission"; -import QBittorrent from "./widgets/service/qbittorrent"; -import Docker from "./widgets/service/docker"; -import Pihole from "./widgets/service/pihole"; -import Rutorrent from "./widgets/service/rutorrent"; -import Jellyfin from "./widgets/service/jellyfin"; -import Speedtest from "./widgets/service/speedtest"; -import Traefik from "./widgets/service/traefik"; -import Jellyseerr from "./widgets/service/jellyseerr"; -import Overseerr from "./widgets/service/overseerr"; -import Npm from "./widgets/service/npm"; -import Tautulli from "./widgets/service/tautulli"; -import CoinMarketCap from "./widgets/service/coinmarketcap"; -import Gotify from "./widgets/service/gotify"; -import Prowlarr from "./widgets/service/prowlarr"; -import Jackett from "./widgets/service/jackett"; -import AdGuard from "./widgets/service/adguard"; -import Authentik from "./widgets/service/authentik"; +const Sonarr = dynamic(() => import("./widgets/service/sonarr")); +const Radarr = dynamic(() => import("./widgets/service/radarr")); +const Lidarr = dynamic(() => import("./widgets/service/lidarr")); +const Readarr = dynamic(() => import("./widgets/service/readarr")); +const Bazarr = dynamic(() => import("./widgets/service/bazarr")); +const Ombi = dynamic(() => import("./widgets/service/ombi")); +const Portainer = dynamic(() => import("./widgets/service/portainer")); +const Emby = dynamic(() => import("./widgets/service/emby")); +const Nzbget = dynamic(() => import("./widgets/service/nzbget")); +const SABnzbd = dynamic(() => import("./widgets/service/sabnzbd")); +const Transmission = dynamic(() => import("./widgets/service/transmission")); +const QBittorrent = dynamic(() => import("./widgets/service/qbittorrent")); +const Docker = dynamic(() => import("./widgets/service/docker")); +const Pihole = dynamic(() => import("./widgets/service/pihole")); +const Rutorrent = dynamic(() => import("./widgets/service/rutorrent")); +const Jellyfin = dynamic(() => import("./widgets/service/jellyfin")); +const Speedtest = dynamic(() => import("./widgets/service/speedtest")); +const Traefik = dynamic(() => import("./widgets/service/traefik")); +const Jellyseerr = dynamic(() => import("./widgets/service/jellyseerr")); +const Overseerr = dynamic(() => import("./widgets/service/overseerr")); +const Npm = dynamic(() => import("./widgets/service/npm")); +const Tautulli = dynamic(() => import("./widgets/service/tautulli")); +const CoinMarketCap = dynamic(() => import("./widgets/service/coinmarketcap")); +const Gotify = dynamic(() => import("./widgets/service/gotify")); +const Prowlarr = dynamic(() => import("./widgets/service/prowlarr")); +const Jackett = dynamic(() => import("./widgets/service/jackett")); +const AdGuard = dynamic(() => import("./widgets/service/adguard")); +const StRelaySrv = dynamic(() => import("./widgets/service/strelaysrv")); +const Mastodon = dynamic(() => import("./widgets/service/mastodon")); +const Authentik = dynamic(() => import("./widgets/service/authentik")); const widgetMappings = { docker: Docker, @@ -57,6 +60,8 @@ const widgetMappings = { prowlarr: Prowlarr, jackett: Jackett, adguard: AdGuard, + strelaysrv: StRelaySrv, + mastodon: Mastodon, authentik: Authentik, }; diff --git a/src/components/services/widgets/service/mastodon.jsx b/src/components/services/widgets/service/mastodon.jsx new file mode 100644 index 00000000..9d2ded4a --- /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/components/services/widgets/service/strelaysrv.jsx b/src/components/services/widgets/service/strelaysrv.jsx new file mode 100644 index 00000000..aaf75daa --- /dev/null +++ b/src/components/services/widgets/service/strelaysrv.jsx @@ -0,0 +1,38 @@ +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 StRelaySrv({ service }) { + const { t } = useTranslation(); + + const config = service.widget; + + const { data: statsData, error: statsError } = useSWR(formatApiUrl(config, `status`)); + + if (statsError) { + return ; + } + + if (!statsData) { + return ( + + + + + + ); + } + + return ( + + + + + + + ); +} diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index 32c9c23f..cb9dd1a6 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 ab4f44b9..c7b3ed22 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"; @@ -81,6 +82,8 @@ const serviceProxyHandlers = { sabnzbd: genericProxyHandler, jackett: genericProxyHandler, adguard: genericProxyHandler, + strelaysrv: genericProxyHandler, + mastodon: genericProxyHandler, // uses X-API-Key (or similar) header auth gotify: credentialedProxyHandler, portainer: credentialedProxyHandler, @@ -99,20 +102,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 1b20a020..db2bba85 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); + + useEffect(() => { + setSettings(initialSettings); + }, [initialSettings, setSettings]); 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 0b18046b..d721cd7b 100644 --- a/src/utils/api-helpers.js +++ b/src/utils/api-helpers.js @@ -24,6 +24,8 @@ const formats = { prowlarr: `{url}/api/v1/{endpoint}`, jackett: `{url}/api/v2.0/{endpoint}?apikey={key}&configured=true`, adguard: `{url}/control/{endpoint}`, + strelaysrv: `{url}/{endpoint}`, + mastodon: `{url}/api/v1/{endpoint}`, authentik: `{url}/api/v3/{endpoint}`, }; diff --git a/src/utils/logger.js b/src/utils/logger.js new file mode 100644 index 00000000..a4d0f1fe --- /dev/null +++ b/src/utils/logger.js @@ -0,0 +1,80 @@ +import { join } from "path"; + +import winston from "winston"; + +const configPath = join(process.cwd(), "config"); + +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: `${configPath}/logs/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 a3b4d50c..7bd136de 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 00000000..d6993b14 --- /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}; +} diff --git a/tailwind.config.js b/tailwind.config.js index 973cfa77..d9b2ea0e 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,4 +1,7 @@ /** @type {import('tailwindcss').Config} */ +const tailwindForms = require("@tailwindcss/forms"); +const tailwindScrollbars = require("tailwind-scrollbar"); + module.exports = { darkMode: "class", content: ["./src/pages/**/*.{js,ts,jsx,tsx}", "./src/components/**/*.{js,ts,jsx,tsx}"], @@ -6,19 +9,19 @@ module.exports = { extend: { colors: { theme: { - ["50"]: "rgb(var(--color-50) / )", - ["100"]: "rgb(var(--color-100) / )", - ["200"]: "rgb(var(--color-200) / )", - ["300"]: "rgb(var(--color-300) / )", - ["400"]: "rgb(var(--color-400) / )", - ["500"]: "rgb(var(--color-500) / )", - ["600"]: "rgb(var(--color-600) / )", - ["700"]: "rgb(var(--color-700) / )", - ["800"]: "rgb(var(--color-800) / )", - ["900"]: "rgb(var(--color-900) / )", + 50: "rgb(var(--color-50) / )", + 100: "rgb(var(--color-100) / )", + 200: "rgb(var(--color-200) / )", + 300: "rgb(var(--color-300) / )", + 400: "rgb(var(--color-400) / )", + 500: "rgb(var(--color-500) / )", + 600: "rgb(var(--color-600) / )", + 700: "rgb(var(--color-700) / )", + 800: "rgb(var(--color-800) / )", + 900: "rgb(var(--color-900) / )", }, }, }, }, - plugins: [require("@tailwindcss/forms")], + plugins: [tailwindForms, tailwindScrollbars], };