Merge branch 'main' into feature/widget_strelaysrv

pull/230/head
Ben Phelps 2 years ago committed by GitHub
commit 122b987fa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

4
.gitignore vendored

@ -19,6 +19,10 @@
.DS_Store
*.pem
# log files
error.log
homepage.log
# debug
npm-debug.log*
yarn-debug.log*

@ -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",

@ -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'}

@ -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"
}
}

@ -157,5 +157,10 @@
"upload": "Hochladen",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -174,5 +174,10 @@
"numConnections": "Connections",
"dataRelayed": "Relayed",
"transferRate": "Rate"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Subida",
"leech": "Compañeros",
"seed": "Semillas"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -168,5 +168,10 @@
"upload": "Envoi",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"jackett": {
"configured": "מוגדר",
"errored": "שגיאה"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"jackett": {
"configured": "Beállított",
"errored": "Hibás"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"leech": "Leech",
"upload": "Upload",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -168,5 +168,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "上传",
"leech": "吸血",
"seed": "做种"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -157,5 +157,10 @@
"upload": "Upload",
"leech": "Leech",
"seed": "Seed"
},
"mastodon": {
"user_count": "Users",
"status_count": "Posts",
"domain_count": "Domains"
}
}

@ -2,13 +2,8 @@ import List from "components/bookmarks/list";
export default function BookmarksGroup({ group }) {
return (
<div
key={group.name}
className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1 p-1"
>
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">
{group.name}
</h2>
<div key={group.name} className="basis-full md:basis-1/2 lg:basis-1/3 xl:basis-1/4 flex-1 p-1">
<h2 className="text-theme-800 dark:text-theme-300 text-xl font-medium">{group.name}</h2>
<List bookmarks={group.bookmarks} />
</div>
);

@ -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 (
<li key={bookmark.name}>
<button
type="button"
onClick={() => window.open(bookmark.href, "_blank").focus()}
className="w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/10 dark:bg-white/10 dark:hover:bg-white/20"
<a
href={bookmark.href}
title={bookmark.name}
target={settings.target ?? "_blank"}
className="block w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-700 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-black/10 dark:shadow-black/20 bg-white/50 hover:bg-theme-300/10 dark:bg-white/10 dark:hover:bg-white/20"
>
<div className="flex">
<div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 hover:text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
@ -17,7 +23,7 @@ export default function Item({ bookmark }) {
<div className="px-2 py-2 truncate text-theme-500 dark:text-theme-400 opacity-50 text-xs">{hostname}</div>
</div>
</div>
</button>
</a>
</li>
);
}

@ -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 (
<li key={service.name}>
@ -37,7 +41,7 @@ export default function Item({ service }) {
(hasLink ? (
<a
href={service.href}
target="_blank"
target={settings.target ?? "_blank"}
rel="noreferrer"
className="flex-shrink-0 flex items-center justify-center w-12 "
>
@ -52,7 +56,7 @@ export default function Item({ service }) {
{hasLink ? (
<a
href={service.href}
target="_blank"
target={settings.target ?? "_blank"}
rel="noreferrer"
className="flex-1 flex items-center justify-between rounded-r-md "
>

@ -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 }) {

@ -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 <Widget error={t("widget.api_error")} />;
}
if (!statsData) {
return (
<Widget>
<Block label={t("mastodon.user_count")} />
<Block label={t("mastodon.status_count")} />
<Block label={t("mastodon.domain_count")} />
</Widget>
);
}
return (
<Widget>
<Block label={t("mastodon.user_count")} value={t("common.number", { value: statsData.stats.user_count })} />
<Block label={t("mastodon.status_count")} value={t("common.number", { value: statsData.stats.status_count })} />
<Block label={t("mastodon.domain_count")} value={t("common.number", { value: statsData.stats.domain_count })} />
</Widget>
);
}

@ -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 }) {
>
<ColorProvider>
<ThemeProvider>
<Component {...pageProps} />
<SettingsProvider>
<Component {...pageProps} />
</SettingsProvider>
</ThemeProvider>
</ColorProvider>
</SWRConfig>

@ -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" });
}

@ -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 <Home settings={settings} />;
return <Home initialSettings={initialSettings} />;
}
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;

@ -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) {

@ -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;

@ -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" });
}

@ -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 <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>;
}
Loading…
Cancel
Save