mirror of
https://github.com/sbondCo/Watcharr.git
synced 2026-08-07 15:25:29 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e7702b16b | |||
| fa2a4a40d0 | |||
| 203e4a31ef | |||
| 5f2c1977ee | |||
| 5860fd2783 | |||
| 3c2cbf55c6 | |||
| 565d90dbf0 |
@@ -0,0 +1,3 @@
|
||||
# Docker dev volume
|
||||
container_data/
|
||||
data/
|
||||
@@ -54,7 +54,7 @@ Feel free to abuse this demo instance (nicely), which runs on the latest `dev` b
|
||||
|
||||
# Set Up
|
||||
|
||||
[Checkout our documentation](https://watcharr.app/docs/category/installation) for an up to date guide on setup! If you hate manuals, but love docker, this [docker-compose.yml](./docker-compose.yml) file is your friend.
|
||||
[Checkout our documentation](https://watcharr.app/docs/category/installation) for an up to date guide on setup! If you hate manuals, but love docker, this [compose.yml](./compose.yml) file is your friend.
|
||||
|
||||
# Community Made Tools
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# This compose file will replace watcharrs
|
||||
# built in UI with a custom one provided in
|
||||
# a named volume (for use with ui-build image).
|
||||
|
||||
services:
|
||||
watcharr:
|
||||
# We dont need to build here too for testing if
|
||||
# a custom ui works, so using latest prod image.
|
||||
image: ghcr.io/sbondco/watcharr:latest
|
||||
container_name: watcharr
|
||||
ports:
|
||||
- 3080:3080
|
||||
volumes:
|
||||
- ./container_data:/data
|
||||
- type: volume
|
||||
source: watcharr-ui
|
||||
target: /ui
|
||||
volume:
|
||||
nocopy: true
|
||||
subpath: build
|
||||
|
||||
volumes:
|
||||
watcharr-ui:
|
||||
external: true
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
sidebar_position: 2
|
||||
description: Install and setup with Docker Compose for access through a subpath.
|
||||
---
|
||||
|
||||
# Docker Compose (subpath)
|
||||
|
||||
Install and setup with Docker Compose for access through a subpath.
|
||||
|
||||
:::info Not a great experience at the moment!
|
||||
|
||||
Currently, hosting Watcharr via a subpath on your server is not very easy (unlike hosting under a subdomain). Hopefully this will change in the future, but as a temporary measure to at least allow hosting under a subpath, this method has been provided.
|
||||
|
||||
The following issue will continue to track this: https://github.com/sbondCo/Watcharr/issues/312
|
||||
|
||||
:::
|
||||
|
||||
## Installing
|
||||
|
||||
### Build UI
|
||||
|
||||
First we have to build the frontend with our subpath provided as an environment variable.
|
||||
|
||||
If you don't want to use `/watcharr` as your subpath, replace the value of `WATCHARR_BASE` before running the command.
|
||||
|
||||
```bash
|
||||
docker run -e WATCHARR_BASE=/watcharr -v watcharr-ui:/ui --rm ghcr.io/sbondco/watcharr-ui-build:latest
|
||||
```
|
||||
|
||||
Once the container has finished running the build script, it will exit and remove itself. The `watcharr-ui` volume will contain the built files.
|
||||
|
||||
### Install Watcharr
|
||||
|
||||
Now we can install Watcharr. You can copy the example below to get started:
|
||||
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
watcharr:
|
||||
# The :latest tag is used for simplicity, it is recommended
|
||||
# to use an actual version, then when updating check the releases for changelogs.
|
||||
image: ghcr.io/sbondco/watcharr:latest
|
||||
container_name: watcharr
|
||||
ports:
|
||||
- 3080:3080
|
||||
volumes:
|
||||
# Contains all of watcharr data (database & cache)
|
||||
- ./data:/data
|
||||
# Use our volume containing built ui files
|
||||
# instead of default ui included in image.
|
||||
- type: volume
|
||||
source: watcharr-ui
|
||||
target: /ui
|
||||
volume:
|
||||
nocopy: true
|
||||
subpath: build
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
watcharr-ui:
|
||||
external: true
|
||||
```
|
||||
|
||||
:::danger first account
|
||||
|
||||
When **first** running Watcharr, make sure only you have access. The first user created will become admin.
|
||||
|
||||
:::
|
||||
|
||||
You can now start `Watcharr` like so:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
If you didn't change the ports in the example, the server will be available at [http://localhost:3080/](http://localhost:3080/).
|
||||
|
||||
## Updating
|
||||
|
||||
:::danger Take care
|
||||
|
||||
We try taking care as to not release breaking changes, however it is still recommended that
|
||||
you lookover changelogs before updating!
|
||||
|
||||
Breaking changes are marked at the top of releases: https://github.com/sbondCo/Watcharr/releases
|
||||
|
||||
:::
|
||||
|
||||
1. Update your built ui files by following the [Build UI](#build-ui) step again.
|
||||
|
||||
2. Update the `image` version in your `compose.yml` file.
|
||||
Skip this step if you are using the `latest` tag.
|
||||
|
||||
```yaml
|
||||
# eg. update v1.19.0 to v1.20.0 (or whatever version you are updating to)
|
||||
image: ghcr.io/sbondco/watcharr:v1.19.0
|
||||
```
|
||||
|
||||
3. Pull the new changes and re-create your container:
|
||||
|
||||
```bash
|
||||
docker compose pull && docker compose down && docker compose up -d
|
||||
```
|
||||
|
||||
And that is it!
|
||||
@@ -9,7 +9,7 @@ description: Install and setup with Docker Compose.
|
||||
|
||||
Installing Watcharr with a docker compose file is easy. You can copy the example below to get started:
|
||||
|
||||
```yaml title="docker-compose.yml"
|
||||
```yaml title="compose.yml"
|
||||
services:
|
||||
watcharr:
|
||||
# The :latest tag is used for simplicity, it is recommended
|
||||
@@ -51,7 +51,7 @@ Breaking changes are marked at the top of releases: https://github.com/sbondCo/W
|
||||
|
||||
Updating your server can be done in two steps:
|
||||
|
||||
1. Update the `image` version in your `docker-compose.yml` file.
|
||||
1. Update the `image` version in your `compose.yml` file.
|
||||
Skip this step if you are using the `latest` tag.
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -80,9 +80,6 @@ const config = {
|
||||
position: "left",
|
||||
label: "Docs",
|
||||
},
|
||||
{
|
||||
type: "docsVersionDropdown",
|
||||
},
|
||||
{
|
||||
href: "https://beta.watcharr.app",
|
||||
label: "Demo",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
This folder is for any _extra_ images that we produce.
|
||||
|
||||
The main watcharr Dockerfile remains in project root.
|
||||
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Frontend
|
||||
#
|
||||
FROM node:20-alpine AS ui
|
||||
|
||||
COPY ./docker/ui-build/entrypoint.sh /entrypoint.sh
|
||||
RUN ["chmod", "+x", "/entrypoint.sh"]
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json vite.config.ts svelte.config.js tsconfig.json ./
|
||||
COPY ./src ./src
|
||||
COPY ./static ./static
|
||||
|
||||
VOLUME /ui
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -0,0 +1,7 @@
|
||||
Image for building watcharr ui.
|
||||
|
||||
Accepts environment variables:
|
||||
|
||||
- `WATCHARR_BASE`
|
||||
|
||||
https://watcharr.app/docs/installation/docker-compose-subpath
|
||||
@@ -0,0 +1,15 @@
|
||||
# To test prod
|
||||
services:
|
||||
watcharr-ui-build:
|
||||
build:
|
||||
context: ../../
|
||||
dockerfile: ./docker/ui-build/Dockerfile
|
||||
container_name: watcharr-ui-build
|
||||
volumes:
|
||||
- watcharr-ui:/ui
|
||||
environment:
|
||||
WATCHARR_BASE: "/sp"
|
||||
|
||||
volumes:
|
||||
watcharr-ui:
|
||||
name: "watcharr-ui"
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This is loosely based on the main containers Dockerfile commands.
|
||||
# Just everything needed to get ui built and ready and nothing else.
|
||||
|
||||
set -x
|
||||
|
||||
echo "wuib: Starting build"
|
||||
|
||||
# Install all deps
|
||||
/usr/local/bin/npm install
|
||||
|
||||
# Run build
|
||||
/usr/local/bin/npm run build
|
||||
|
||||
# Remove any existing build files from volume
|
||||
rm -rf /ui/build
|
||||
|
||||
# Move build folder to /ui
|
||||
mv /app/build /ui
|
||||
# Move package.json and lock file to /ui/build for final npm ci call
|
||||
mv /app/package.json /app/package-lock.json /ui/build
|
||||
|
||||
# cd to build files final destination and install dependencies for production
|
||||
cd /ui/build && /usr/local/bin/npm ci --omit=dev --ignore-scripts=true
|
||||
@@ -645,6 +645,54 @@
|
||||
d="M368 192h-16v-80a96 96 0 10-192 0v80h-16a64.07 64.07 0 00-64 64v176a64.07 64.07 0 0064 64h224a64.07 64.07 0 0064-64V256a64.07 64.07 0 00-64-64zm-48 0H192v-80a64 64 0 11128 0z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "github"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 24 25"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M12.301 0h.093c2.242 0 4.34.613 6.137 1.68l-.055-.031a12.35 12.35 0 0 1 4.449 4.422l.031.058a12.2 12.2 0 0 1 1.654 6.166c0 5.406-3.483 10-8.327 11.658l-.087.026a.72.72 0 0 1-.642-.113l.002.001a.62.62 0 0 1-.208-.466v-.014v.001l.008-1.226q.008-1.178.008-2.154a2.84 2.84 0 0 0-.833-2.274a11 11 0 0 0 1.718-.305l-.076.017a6.5 6.5 0 0 0 1.537-.642l-.031.017a4.5 4.5 0 0 0 1.292-1.058l.006-.007a4.9 4.9 0 0 0 .84-1.645l.009-.035a7.9 7.9 0 0 0 .329-2.281l-.001-.136v.007l.001-.072a4.73 4.73 0 0 0-1.269-3.23l.003.003c.168-.44.265-.948.265-1.479a4.25 4.25 0 0 0-.404-1.814l.011.026a2.1 2.1 0 0 0-1.31.181l.012-.005a8.6 8.6 0 0 0-1.512.726l.038-.022l-.609.384c-.922-.264-1.981-.416-3.075-.416s-2.153.152-3.157.436l.081-.02q-.256-.176-.681-.433a9 9 0 0 0-1.272-.595l-.066-.022A2.17 2.17 0 0 0 5.837 5.1l.013-.002a4.2 4.2 0 0 0-.393 1.788c0 .531.097 1.04.275 1.509l-.01-.029a4.72 4.72 0 0 0-1.265 3.303v-.004l-.001.13c0 .809.12 1.591.344 2.327l-.015-.057c.189.643.476 1.202.85 1.693l-.009-.013a4.4 4.4 0 0 0 1.267 1.062l.022.011c.432.252.933.465 1.46.614l.046.011c.466.125 1.024.227 1.595.284l.046.004c-.431.428-.718 1-.784 1.638l-.001.012a3 3 0 0 1-.699.236l-.021.004c-.256.051-.549.08-.85.08h-.066h.003a1.9 1.9 0 0 1-1.055-.348l.006.004a2.84 2.84 0 0 1-.881-.986l-.007-.015a2.6 2.6 0 0 0-.768-.827l-.009-.006a2.3 2.3 0 0 0-.776-.38l-.016-.004l-.32-.048a1.05 1.05 0 0 0-.471.074l.007-.003q-.128.072-.08.184q.058.128.145.225l-.001-.001q.092.108.205.19l.003.002l.112.08c.283.148.516.354.693.603l.004.006c.191.237.359.505.494.792l.01.024l.16.368c.135.402.38.738.7.981l.005.004c.3.234.662.402 1.057.478l.016.002c.33.064.714.104 1.106.112h.007q.069.002.15.002q.392 0 .767-.062l-.027.004l.368-.064q0 .609.008 1.418t.008.873v.014c0 .185-.08.351-.208.466h-.001a.72.72 0 0 1-.645.111l.005.001C3.486 22.286.006 17.692.006 12.285c0-2.268.612-4.393 1.681-6.219l-.032.058a12.35 12.35 0 0 1 4.422-4.449l.058-.031a11.9 11.9 0 0 1 6.073-1.645h.098h-.005zm-7.64 17.666q.048-.112-.112-.192q-.16-.048-.208.032q-.048.112.112.192q.144.096.208-.032m.497.545q.112-.08-.032-.256q-.16-.144-.256-.048q-.112.08.032.256q.159.157.256.047zm.48.72q.144-.112 0-.304q-.128-.208-.272-.096q-.144.08 0 .288t.272.112m.672.673q.128-.128-.064-.304q-.192-.192-.32-.048q-.144.128.064.304q.192.192.32.044zm.913.4q.048-.176-.208-.256q-.24-.064-.304.112t.208.24q.24.097.304-.096m1.009.08q0-.208-.272-.176q-.256 0-.256.176q0 .208.272.176q.256.001.256-.175zm.929-.16q-.032-.176-.288-.144q-.256.048-.224.24t.288.128t.225-.224z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "website"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 2048 2048"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M1024 0q141 0 272 36t245 103t207 160t160 208t103 245t37 272q0 141-36 272t-103 245t-160 207t-208 160t-245 103t-272 37q-141 0-272-36t-245-103t-207-160t-160-208t-103-244t-37-273q0-141 36-272t103-245t160-207t208-160T751 37t273-37m0 1920q123 0 237-32t214-90t182-141t140-181t91-214t32-238q0-123-32-237t-90-214t-141-182t-181-140t-214-91t-238-32q-123 0-237 32t-214 90t-182 141t-140 181t-91 214t-32 238q0 123 32 237t90 214t141 182t181 140t214 91t238 32m597-880l48-144h75l-85 256h-75l-48-144l-48 144h-75l-85-256h75l48 144l48-144h74zm-464-144h75l-85 256h-75l-48-144l-48 144h-75l-85-256h75l48 144l48-144h74l48 144zm-512 0h75l-85 256h-75l-48-144l-48 144h-75l-85-256h75l48 144l48-144h74l48 144z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "tmdb"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 185.04 133.4"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M51.06,66.7h0A17.67,17.67,0,0,1,68.73,49h-.1A17.67,17.67,0,0,1,86.3,66.7h0A17.67,17.67,0,0,1,68.63,84.37h.1A17.67,17.67,0,0,1,51.06,66.7Zm82.67-31.33h32.9A17.67,17.67,0,0,0,184.3,17.7h0A17.67,17.67,0,0,0,166.63,0h-32.9A17.67,17.67,0,0,0,116.06,17.7h0A17.67,17.67,0,0,0,133.73,35.37Zm-113,98h63.9A17.67,17.67,0,0,0,102.3,115.7h0A17.67,17.67,0,0,0,84.63,98H20.73A17.67,17.67,0,0,0,3.06,115.7h0A17.67,17.67,0,0,0,20.73,133.37Zm83.92-49h6.25L125.5,49h-8.35l-8.9,23.2h-.1L99.4,49H90.5Zm32.45,0h7.8V49h-7.8Zm22.2,0h24.95V77.2H167.1V70h15.35V62.8H167.1V56.2h16.25V49h-24ZM10.1,35.4h7.8V6.9H28V0H0V6.9H10.1ZM39,35.4h7.8V20.1H61.9V35.4h7.8V0H61.9V13.2H46.75V0H39Zm41.25,0h25V28.2H88V21h15.35V13.8H88V7.2h16.25V0h-24Zm-79,49H9V57.25h.1l9,27.15H24l9.3-27.15h.1V84.4h7.8V49H29.45l-8.2,23.1h-.1L13,49H1.2Zm112.09,49H126a24.59,24.59,0,0,0,7.56-1.15,19.52,19.52,0,0,0,6.35-3.37,16.37,16.37,0,0,0,4.37-5.5A16.91,16.91,0,0,0,146,115.8a18.5,18.5,0,0,0-1.68-8.25,15.1,15.1,0,0,0-4.52-5.53A18.55,18.55,0,0,0,133.07,99,33.54,33.54,0,0,0,125,98H113.29Zm7.81-28.2h4.6a17.43,17.43,0,0,1,4.67.62,11.68,11.68,0,0,1,3.88,1.88,9,9,0,0,1,2.62,3.18,9.87,9.87,0,0,1,1,4.52,11.92,11.92,0,0,1-1,5.08,8.69,8.69,0,0,1-2.67,3.34,10.87,10.87,0,0,1-4,1.83,21.57,21.57,0,0,1-5,.55H121.1Zm36.14,28.2h14.5a23.11,23.11,0,0,0,4.73-.5,13.38,13.38,0,0,0,4.27-1.65,9.42,9.42,0,0,0,3.1-3,8.52,8.52,0,0,0,1.2-4.68,9.16,9.16,0,0,0-.55-3.2,7.79,7.79,0,0,0-1.57-2.62,8.38,8.38,0,0,0-2.45-1.85,10,10,0,0,0-3.18-1v-.1a9.28,9.28,0,0,0,4.43-2.82,7.42,7.42,0,0,0,1.67-5,8.34,8.34,0,0,0-1.15-4.65,7.88,7.88,0,0,0-3-2.73,12.9,12.9,0,0,0-4.17-1.3,34.42,34.42,0,0,0-4.63-.32h-13.2Zm7.8-28.8h5.3a10.79,10.79,0,0,1,1.85.17,5.77,5.77,0,0,1,1.7.58,3.33,3.33,0,0,1,1.23,1.13,3.22,3.22,0,0,1,.47,1.82,3.63,3.63,0,0,1-.42,1.8,3.34,3.34,0,0,1-1.13,1.2,4.78,4.78,0,0,1-1.57.65,8.16,8.16,0,0,1-1.78.2H165Zm0,14.15h5.9a15.12,15.12,0,0,1,2.05.15,7.83,7.83,0,0,1,2,.55,4,4,0,0,1,1.58,1.17,3.13,3.13,0,0,1,.62,2,3.71,3.71,0,0,1-.47,1.95,4,4,0,0,1-1.23,1.3,4.78,4.78,0,0,1-1.67.7,8.91,8.91,0,0,1-1.83.2h-7Z"
|
||||
/>
|
||||
</svg>
|
||||
{:else if i === "igdb"}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={wh}
|
||||
height={wh}
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M24 6.228H0v11.543a89 89 0 0 1 2.271-.333a74 74 0 0 1 17.038-.28c1.57.153 3.134.363 4.69.614zm-.706.707v10.013a74.8 74.8 0 0 0-22.588 0V6.934zM7.729 8.84a2.62 2.62 0 0 0-1.857.72a2.55 2.55 0 0 0-.73 1.33c-.098.5-.063 1.03.112 1.51c.177.488.515.917.954 1.196c.547.354 1.224.472 1.865.401a3.24 3.24 0 0 0 1.786-.777c-.003-.724.002-1.449-.002-2.173c-.725.004-1.45-.002-2.174.003c.003.317 0 .634.001.951h1.105c.002.236 0 .473.002.71a1.7 1.7 0 0 1-.932.298c-.32.02-.65-.05-.922-.225a1.46 1.46 0 0 1-.59-.744c-.18-.499-.134-1.085.163-1.53c.23-.355.619-.61 1.043-.647a1.8 1.8 0 0 1 1.012.206c.152.082.286.192.424.295q.344-.42.692-.838a3 3 0 0 0-.595-.403c-.418-.212-.892-.285-1.357-.283m11.66.086q-.14.002-.28 0c-.68.002-1.359-.004-2.038.003c.003 1.666 0 3.332.002 4.998h2.497q.36-.003.709-.097c.276-.076.546-.208.742-.422c.194-.208.297-.492.304-.776c.016-.278-.032-.572-.195-.804c-.175-.252-.453-.408-.734-.514c.211-.122.407-.285.521-.505c.134-.246.149-.535.117-.807a1.16 1.16 0 0 0-.436-.73c-.264-.207-.599-.304-.93-.334a3 3 0 0 0-.279-.012m-16.715 0v5.002h1.102V8.927q-.552-.002-1.102 0zm8.524 0v5.002h2.016a2.9 2.9 0 0 0 1.07-.211a2.44 2.44 0 0 0 1.174-.993c.34-.555.429-1.244.292-1.876a2.37 2.37 0 0 0-.828-1.338c-.478-.387-1.096-.577-1.707-.584zm6.949.967c.392.002.784-.001 1.176.002c.183.011.38.054.51.19c.11.112.136.28.112.43a.44.44 0 0 1-.22.316a1.1 1.1 0 0 1-.483.116c-.365.002-.73-.001-1.094.001l-.001-1.054zm-5.031.026c.28 0 .567.053.815.19c.274.149.491.396.607.685c.113.272.138.574.107.865a1.46 1.46 0 0 1-.335.786a1.43 1.43 0 0 1-.865.466c-.168.031-.34.022-.51.023h-.632V9.92zm5.03 1.948h1.36c.174.006.354.035.505.127a.45.45 0 0 1 .212.308c.025.15.004.32-.099.44c-.102.12-.258.176-.409.2c-.172.032-.348.02-.522.022c-.35-.001-.698.002-1.047-.001z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
+22
-1
@@ -100,10 +100,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
a.menu-footer,
|
||||
button.menu-footer {
|
||||
font-size: 11px;
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
transition: inherit;
|
||||
color: $text-color-accent;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
display: inline;
|
||||
font-weight: initial;
|
||||
width: auto;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
text-decoration: underline;
|
||||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
margin-top: 8px;
|
||||
font-size: 11px;
|
||||
color: gray;
|
||||
color: $text-color-accent;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PublicUser } from "@/types";
|
||||
import { toBaseUrl } from "./util/url";
|
||||
|
||||
interface Props {
|
||||
users: PublicUser[];
|
||||
@@ -13,7 +14,7 @@
|
||||
<ul>
|
||||
{#each users as user}
|
||||
<li title={user.username}>
|
||||
<a href="/lists/{user.id}/{user.username}">
|
||||
<a href={toBaseUrl(`/lists/${user.id}/${user.username}`)}>
|
||||
<span>{user.username}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { getLatestWatchedInTv } from "./util/helpers";
|
||||
import { notify } from "./util/notify";
|
||||
import { untrack } from "svelte";
|
||||
import { toBaseUrl } from "./util/url";
|
||||
|
||||
interface Props {
|
||||
list: Watched[];
|
||||
@@ -296,7 +297,7 @@
|
||||
<h4 class="norm">
|
||||
Try searching for something you would like to add.
|
||||
</h4>
|
||||
<button onclick={() => goto("/import")}>Import</button>
|
||||
<button onclick={() => goto(toBaseUrl("/import"))}>Import</button>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import Spinner from "../Spinner.svelte";
|
||||
import { clearWatcharrData } from ".";
|
||||
import { goto } from "$app/navigation";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
@@ -34,7 +35,7 @@
|
||||
|
||||
function logout() {
|
||||
clearWatcharrData();
|
||||
goto("/login?noAuto=1");
|
||||
goto(toBaseUrl("/login?noAuto=1"));
|
||||
}
|
||||
|
||||
function proxyLogout() {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
<script lang="ts">
|
||||
import Icon from "../Icon.svelte";
|
||||
import Modal from "../Modal.svelte";
|
||||
|
||||
interface Props {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
let { onClose }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Modal title="About" {onClose} maxWidth="400px">
|
||||
<div class="content">
|
||||
<div class="inner">
|
||||
<p>
|
||||
An open source project that helps you keep the content you are watching
|
||||
or playing organized and places you in control.
|
||||
</p>
|
||||
<div class="horizontal-icon-list">
|
||||
<a
|
||||
href="https://github.com/sbondCo/Watcharr"
|
||||
target="_blank"
|
||||
title="Github"
|
||||
>
|
||||
<Icon i="github" wh={60} />
|
||||
Source<br />
|
||||
Code
|
||||
</a>
|
||||
<a
|
||||
href="https://watcharr.app/"
|
||||
target="_blank"
|
||||
title="Watcharr Website & Documentation"
|
||||
>
|
||||
<Icon i="website" wh={60} />
|
||||
Website<br />
|
||||
& Docs
|
||||
</a>
|
||||
</div>
|
||||
<h5 class="norm">Watcharr uses the following media databases:</h5>
|
||||
<div class="horizontal-icon-list">
|
||||
<a href="https://www.themoviedb.org/" target="_blank" title="TMDB">
|
||||
<Icon i="tmdb" wh={60} />
|
||||
</a>
|
||||
<a href="https://www.igdb.com/" target="_blank" title="IGDB">
|
||||
<Icon i="igdb" wh={60} />
|
||||
</a>
|
||||
</div>
|
||||
<h5 class="norm">
|
||||
<!-- Linking to the file in `dev` branch would break every old version of
|
||||
Watcharr if the file were ever moved in the future. Using the current version
|
||||
as the branch should ensure this file is always accessible (but only as up to
|
||||
date as the current version) -->
|
||||
<a
|
||||
href="https://github.com/sbondCo/Watcharr/blob/v{__WATCHARR_VERSION__}/ATTRIBUTION.md"
|
||||
target="_blank"
|
||||
>
|
||||
See our attribution file.
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style lang="scss">
|
||||
p {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
||||
h5.norm {
|
||||
margin: 8px 0px;
|
||||
|
||||
> a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
div.horizontal-icon-list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-bottom: 20px;
|
||||
padding-top: 5px;
|
||||
|
||||
> a {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
margin: 0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
transition: opacity 150ms ease-in;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -7,9 +7,12 @@
|
||||
import { goto } from "$app/navigation";
|
||||
import { clearWatcharrData } from "../logout";
|
||||
import { notify } from "../util/notify";
|
||||
import AboutModal from "./AboutModal.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
let user = $derived(store.userInfo);
|
||||
let proxyUserLogoutShown = $state(false);
|
||||
let aboutModalOpen = $state(false);
|
||||
|
||||
function logout() {
|
||||
if (user?.type === UserType.Proxy) {
|
||||
@@ -18,23 +21,23 @@
|
||||
return;
|
||||
}
|
||||
clearWatcharrData();
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
}
|
||||
|
||||
function profile() {
|
||||
goto("/profile");
|
||||
goto(toBaseUrl("/profile"));
|
||||
}
|
||||
|
||||
function serverSettings() {
|
||||
goto("/server");
|
||||
goto(toBaseUrl("/server"));
|
||||
}
|
||||
|
||||
function userManagement() {
|
||||
goto("/manage_users");
|
||||
goto(toBaseUrl("/manage_users"));
|
||||
}
|
||||
|
||||
function requestManagement() {
|
||||
goto("/arr_requests");
|
||||
goto(toBaseUrl("/arr_requests"));
|
||||
}
|
||||
|
||||
function shareWatchedList() {
|
||||
@@ -42,7 +45,9 @@
|
||||
const ud = parseTokenPayload();
|
||||
console.log(ud);
|
||||
if (ud?.userId && ud?.username) {
|
||||
const shareLink = `${window.location.origin}/lists/${ud.userId}/${ud.username}`;
|
||||
const shareLink = `${window.location.origin}${toBaseUrl(
|
||||
`/lists/${ud.userId}/${ud.username}`,
|
||||
)}`;
|
||||
navigator.clipboard
|
||||
.writeText(shareLink)
|
||||
.then(() => {
|
||||
@@ -61,6 +66,10 @@
|
||||
notify({ id: nid, type: "error", text: "Failed to get link" });
|
||||
}
|
||||
}
|
||||
|
||||
function closeAbout() {
|
||||
aboutModalOpen = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Menu conf={{ arrowRight: "10px" }}>
|
||||
@@ -85,5 +94,26 @@
|
||||
{#if proxyUserLogoutShown}
|
||||
<ProxyUserLogoutModal onClose={() => (proxyUserLogoutShown = false)} />
|
||||
{/if}
|
||||
<span>v{__WATCHARR_VERSION__}</span>
|
||||
<span>
|
||||
<button
|
||||
class="menu-footer"
|
||||
onclick={() => {
|
||||
aboutModalOpen = !aboutModalOpen;
|
||||
}}
|
||||
>
|
||||
about
|
||||
</button>
|
||||
|
|
||||
<a
|
||||
class="menu-footer"
|
||||
href="https://github.com/sbondCo/Watcharr/releases"
|
||||
target="_blank"
|
||||
>
|
||||
v{__WATCHARR_VERSION__}
|
||||
</a>
|
||||
</span>
|
||||
</Menu>
|
||||
|
||||
{#if aboutModalOpen}
|
||||
<AboutModal onClose={closeAbout} />
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { store } from "@/store.svelte";
|
||||
import Menu from "../Menu.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
close: () => {};
|
||||
@@ -15,7 +16,9 @@
|
||||
<div class="list">
|
||||
{#each store.follows as f}
|
||||
<a
|
||||
href="/lists/{f.followedUser.id}/{f.followedUser.username}"
|
||||
href={toBaseUrl(
|
||||
`/lists/${f.followedUser.id}/${f.followedUser.username}`,
|
||||
)}
|
||||
onclick={() => close()}
|
||||
>
|
||||
{f.followedUser.username}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import PosterRating from "./PosterRating.svelte";
|
||||
import { decode } from "blurhash";
|
||||
import ExtraDetails from "./ExtraDetails.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
id?: number | undefined; // Watched list id
|
||||
@@ -72,7 +73,7 @@
|
||||
const poster = media.poster?.path
|
||||
? `${baseURL}/${media.poster.path}`
|
||||
: `https://images.igdb.com/igdb/image/upload/t_cover_big/${media.coverId}.jpg`;
|
||||
const link = media.id ? `/game/${media.id}` : undefined;
|
||||
const link = media.id ? toBaseUrl(`/game/${media.id}`) : undefined;
|
||||
const dateStr = media.firstReleaseDate;
|
||||
const year = dateStr ? new Date(dateStr).getFullYear() : undefined;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
addClassToParent,
|
||||
calculateTransformOrigin,
|
||||
} from "@/lib/util/helpers";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
id: number | undefined;
|
||||
@@ -24,7 +25,7 @@
|
||||
const poster = path
|
||||
? `https://image.tmdb.org/t/p/w300_and_h450_bestv2${path}`
|
||||
: undefined;
|
||||
const link = id ? `/person/${id}` : undefined;
|
||||
const link = id ? toBaseUrl(`/person/${id}`) : undefined;
|
||||
</script>
|
||||
|
||||
<!-- Quick fix to ignore error, should be fixed -->
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import PosterStatus from "./PosterStatus.svelte";
|
||||
import PosterRating from "./PosterRating.svelte";
|
||||
import ExtraDetails from "./ExtraDetails.svelte";
|
||||
import { toBaseUrl } from "../util/url";
|
||||
|
||||
interface Props {
|
||||
id?: number | undefined; // Watched list id
|
||||
@@ -84,7 +85,7 @@
|
||||
: `https://image.tmdb.org/t/p/w500${media.poster_path}`,
|
||||
);
|
||||
let link = $derived(
|
||||
media.id ? `/${media.media_type}/${media.id}` : undefined,
|
||||
media.id ? toBaseUrl(`/${media.media_type}/${media.id}`) : undefined,
|
||||
);
|
||||
let dateStr = $derived(media.release_date || media.first_air_date);
|
||||
let year = $derived(dateStr ? new Date(dateStr).getFullYear() : undefined);
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ import {
|
||||
import axios from "axios";
|
||||
import { notify, unNotify } from "./notify";
|
||||
import { browser } from "$app/environment";
|
||||
import { toBaseUrl } from "./url";
|
||||
const { MODE } = import.meta.env;
|
||||
|
||||
export const baseURL =
|
||||
@@ -23,7 +24,7 @@ export const baseURL =
|
||||
? browser
|
||||
? `${location.protocol}//${location.hostname}:3080/api`
|
||||
: "http://127.0.0.1:3080/api"
|
||||
: "/api";
|
||||
: toBaseUrl("/api");
|
||||
console.log("api: baseURL constructed:", baseURL);
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { base } from "$app/paths";
|
||||
|
||||
/**
|
||||
* Takes in a `path` and prefixes it with `base`.
|
||||
* This enables support for using base paths to access
|
||||
* the frontend by fixing all absolute paths passed through.
|
||||
*/
|
||||
export function toBaseUrl(path: string) {
|
||||
return base + path;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto } from "$app/navigation";
|
||||
import { base } from "$app/paths";
|
||||
import { page } from "$app/state";
|
||||
import Icon from "@/lib/Icon.svelte";
|
||||
import PageError from "@/lib/PageError.svelte";
|
||||
@@ -12,6 +13,7 @@
|
||||
import SortMenu from "@/lib/nav/SortMenu.svelte";
|
||||
import TagMenu from "@/lib/tag/TagMenu.svelte";
|
||||
import { isTouch } from "@/lib/util/helpers";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import { store, defaultSort } from "@/store.svelte";
|
||||
import axios from "axios";
|
||||
import { onMount } from "svelte";
|
||||
@@ -34,7 +36,7 @@
|
||||
|
||||
function handleProfileClick() {
|
||||
if (!localStorage.getItem("token")) {
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
} else {
|
||||
closeAllSubMenus("sub");
|
||||
subMenuShown = !subMenuShown;
|
||||
@@ -64,7 +66,7 @@
|
||||
// Using autofocus seems to work. Disables after goto runs.
|
||||
// https://github.com/sbondCo/Watcharr/issues/169
|
||||
target.autofocus = true;
|
||||
goto(`/search?q=${encodeURIComponent(query)}`).then(() => {
|
||||
goto(toBaseUrl(`/search?q=${encodeURIComponent(query)}`)).then(() => {
|
||||
// Use mainSearchEl if nav not split, otherwise use ev target.
|
||||
if (
|
||||
!document.body.classList.contains("split-nav") &&
|
||||
@@ -112,7 +114,7 @@
|
||||
store.tags = ts.data;
|
||||
}
|
||||
} else {
|
||||
goto("/login?again=1");
|
||||
goto(toBaseUrl("/login?again=1"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +189,7 @@
|
||||
|
||||
<nav bind:this={navEl}>
|
||||
<div class="wrapper">
|
||||
<a href="/">
|
||||
<a href={toBaseUrl("/")}>
|
||||
<span class="large">Watcharr</span>
|
||||
<span class="small">W</span>
|
||||
</a>
|
||||
@@ -278,7 +280,7 @@
|
||||
{#if tagMenuShown}
|
||||
<TagMenu
|
||||
onTagClick={(tag) => {
|
||||
goto(`/tag/${tag.id}`);
|
||||
goto(toBaseUrl(`/tag/${tag.id}`));
|
||||
tagMenuShown = false;
|
||||
}}
|
||||
showManageBtn={true}
|
||||
@@ -286,7 +288,7 @@
|
||||
{/if}
|
||||
<button
|
||||
class="plain other discover"
|
||||
onclick={() => goto("/discover")}
|
||||
onclick={() => goto(toBaseUrl("/discover"))}
|
||||
use:tooltip={{ text: "Discover", pos: "bot" }}
|
||||
>
|
||||
<Icon i="compass" wh={26} />
|
||||
|
||||
@@ -7,6 +7,7 @@ import axios from "axios";
|
||||
import { baseURL } from "@/lib/util/api";
|
||||
import { notify } from "@/lib/util/notify";
|
||||
import { clearWatcharrData } from "@/lib/logout";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config) => {
|
||||
@@ -18,7 +19,7 @@ axios.interceptors.request.use(
|
||||
// Don't require token check if going to auth route (login/register)
|
||||
if (!token && !config.url?.includes("/auth")) {
|
||||
console.error("No token, going to login. Endpoint:", config.url);
|
||||
goto("/login?again=1");
|
||||
goto(toBaseUrl("/login?again=1"));
|
||||
throw new axios.Cancel("No auth token found");
|
||||
}
|
||||
config.headers.set("Authorization", token);
|
||||
@@ -40,7 +41,7 @@ axios.interceptors.response.use(
|
||||
console.error("Recieved 401 response, going to login.");
|
||||
notify({ text: "Request Authorization Failed!", type: "error" });
|
||||
clearWatcharrData();
|
||||
goto("/login?again=1");
|
||||
goto(toBaseUrl("/login?again=1"));
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { baseURL } from "@/lib/util/api";
|
||||
import { toRelativeDate } from "@/lib/util/helpers";
|
||||
import { notify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import {
|
||||
type ArrRequestResponse,
|
||||
type TMDBMovieDetails,
|
||||
@@ -94,7 +95,7 @@
|
||||
<h2 class="norm">
|
||||
<a
|
||||
data-sveltekit-preload-data="tap"
|
||||
href={`/${r.content.type}/${r.content.tmdbId}`}
|
||||
href={toBaseUrl(`/${r.content.type}/${r.content.tmdbId}`)}
|
||||
class="plain"
|
||||
>
|
||||
{r.content.title}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
TodoMoviesMovie,
|
||||
} from "@/types";
|
||||
import Icon from "@/lib/Icon.svelte";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let isDragOver = $state(false);
|
||||
let isLoading = $state(false);
|
||||
@@ -83,7 +84,7 @@
|
||||
data: r.result.toString(),
|
||||
type,
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
}
|
||||
},
|
||||
false,
|
||||
@@ -255,7 +256,7 @@
|
||||
data: JSON.stringify(toImport),
|
||||
type: "movary",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read files!" });
|
||||
@@ -331,7 +332,7 @@
|
||||
data: JSON.stringify(toImport),
|
||||
type: "watcharr",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read file!" });
|
||||
@@ -380,7 +381,7 @@
|
||||
data: r.result.toString(),
|
||||
type: "myanimelist",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
}
|
||||
},
|
||||
false,
|
||||
@@ -539,7 +540,7 @@
|
||||
data: JSON.stringify(toImport),
|
||||
type: "ryot",
|
||||
};
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read file!" });
|
||||
@@ -657,7 +658,7 @@
|
||||
type: "todomovies",
|
||||
};
|
||||
|
||||
goto("/import/process");
|
||||
goto(toBaseUrl("/import/process"));
|
||||
} catch (err) {
|
||||
isLoading = false;
|
||||
notify({ type: "error", text: "Failed to read files!" });
|
||||
@@ -667,7 +668,7 @@
|
||||
|
||||
onMount(() => {
|
||||
if (!localStorage.getItem("token")) {
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -702,7 +703,7 @@
|
||||
filesSelected={(f) => processFiles(f, "tmdb")}
|
||||
/>
|
||||
|
||||
<button class="plain" onclick={() => goto("/import/trakt")}>
|
||||
<button class="plain" onclick={() => goto(toBaseUrl("/import/trakt"))}>
|
||||
<Icon i="trakt" wh="100%" />
|
||||
<h4 class="norm">Trakt Import</h4>
|
||||
</button>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
import { onDestroy } from "svelte";
|
||||
import papa from "papaparse";
|
||||
import Status from "@/lib/Status.svelte";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
interface ImportedListItemMultiProblem {
|
||||
original: ImportedList;
|
||||
@@ -58,7 +59,7 @@
|
||||
const list = store.importedList;
|
||||
if (!list) {
|
||||
console.log("import/process, no list, returning to /import");
|
||||
goto("/import");
|
||||
goto(toBaseUrl("/import"));
|
||||
return;
|
||||
}
|
||||
console.log("getList", list);
|
||||
@@ -431,14 +432,14 @@
|
||||
) {
|
||||
// Some items failed.. go to some-failed
|
||||
store.parsedImportedList = rList;
|
||||
goto("/import/some-failed");
|
||||
goto(toBaseUrl("/import/some-failed"));
|
||||
} else {
|
||||
notify({
|
||||
type: "success",
|
||||
text: "All content successfully imported! Try refreshing if you are missing data.",
|
||||
time: 15000,
|
||||
});
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,7 +678,8 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="btns">
|
||||
<button onclick={() => goto("/import")}><Icon i="arrow" />Back</button
|
||||
<button onclick={() => goto(toBaseUrl("/import"))}
|
||||
><Icon i="arrow" />Back</button
|
||||
>
|
||||
<button onclick={() => changeAllStatuses()} disabled={isImporting}>
|
||||
Change Statuses
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import { store } from "@/store.svelte";
|
||||
import { ImportResponseType, type ImportedList } from "@/types";
|
||||
import { onMount } from "svelte";
|
||||
@@ -36,7 +37,7 @@
|
||||
}
|
||||
console.log("failedlen", failed.length);
|
||||
} else {
|
||||
goto("/import");
|
||||
goto(toBaseUrl("/import"));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import SyncModal from "./modals/SyncModal.svelte";
|
||||
import RegionDropDown from "@/lib/RegionDropDown.svelte";
|
||||
import RatingSetting from "@/lib/rating/RatingSetting.svelte";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let user = $derived(store.userInfo);
|
||||
let settings = $derived(store.userSettings);
|
||||
@@ -345,7 +346,7 @@
|
||||
<RatingSetting />
|
||||
|
||||
<div class="row btns">
|
||||
<button onclick={() => goto("/import")}>Import</button>
|
||||
<button onclick={() => goto(toBaseUrl("/import"))}>Import</button>
|
||||
<button onclick={() => downloadWatchedList()} disabled={exportDisabled}
|
||||
>Export</button
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { notify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
import { store } from "@/store.svelte";
|
||||
import { UserPermission } from "@/types";
|
||||
import axios from "axios";
|
||||
@@ -32,7 +33,7 @@
|
||||
if (store.userInfo) {
|
||||
store.userInfo.permissions = UserPermission.PERM_ADMIN;
|
||||
}
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
import Icon from "@/lib/Icon.svelte";
|
||||
import { afterNavigate, goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { toBaseUrl } from "@/lib/util/url.js";
|
||||
|
||||
type GameWithMediaType = GameSearch & { media_type: "game" };
|
||||
type CombinedResult =
|
||||
@@ -278,7 +279,7 @@
|
||||
data[0],
|
||||
);
|
||||
|
||||
goto(`/game/${data[0].id}`);
|
||||
goto(toBaseUrl(`/game/${data[0].id}`));
|
||||
return;
|
||||
}
|
||||
allSearchResults.push(...data);
|
||||
@@ -290,7 +291,7 @@
|
||||
// assuming that people paste the id in, this should work
|
||||
// without the debounce going to an incomplete id.
|
||||
// Flesh out if anyone has issues.
|
||||
goto(`/${extProvider.provider}/${extProvider.id}`);
|
||||
goto(toBaseUrl(`/${extProvider.provider}/${extProvider.id}`));
|
||||
return;
|
||||
} else {
|
||||
// Else call tmdb `external id` endpoint
|
||||
@@ -324,7 +325,11 @@
|
||||
mediaType,
|
||||
);
|
||||
} else {
|
||||
goto(`/${data.results[0].media_type}/${data.results[0].id}`);
|
||||
goto(
|
||||
toBaseUrl(
|
||||
`/${data.results[0].media_type}/${data.results[0].id}`,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import RegionDropDown from "@/lib/RegionDropDown.svelte";
|
||||
import TaskScheduleModal from "./modals/TaskScheduleModal.svelte";
|
||||
import TrustedHeaderAuthModal from "./modals/TrustedHeaderAuthModal.svelte";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let serverConfig: ServerConfig | undefined = $state();
|
||||
let jellyfinOrEmby = $derived(serverConfig?.USE_EMBY ? "Emby" : "Jellyfin");
|
||||
@@ -108,7 +109,12 @@
|
||||
{#await getServerStats()}
|
||||
<Spinner />
|
||||
{:then stats}
|
||||
<Stat name="Users" value={stats.users} href="/manage_users" large />
|
||||
<Stat
|
||||
name="Users"
|
||||
value={stats.users}
|
||||
href={toBaseUrl("/manage_users")}
|
||||
large
|
||||
/>
|
||||
<Stat name="Private Users" value={stats.privateUsers} large />
|
||||
<Stat name="Watched Movies" value={stats.watchedMovies} large />
|
||||
<Stat name="Watched Shows" value={stats.watchedShows} large />
|
||||
@@ -118,14 +124,14 @@
|
||||
<Stat
|
||||
name="Most Watched Movie"
|
||||
value={stats.mostWatchedMovie.title}
|
||||
href="/movie/{stats.mostWatchedMovie.tmdbId}"
|
||||
href={toBaseUrl(`/movie/${stats.mostWatchedMovie.tmdbId}`)}
|
||||
/>
|
||||
{/if}
|
||||
{#if stats.mostWatchedShow?.title}
|
||||
<Stat
|
||||
name="Most Watched Show"
|
||||
value={stats.mostWatchedShow.title}
|
||||
href="/tv/{stats.mostWatchedShow.tmdbId}"
|
||||
href={toBaseUrl(`/tv/${stats.mostWatchedShow.tmdbId}`)}
|
||||
/>
|
||||
{/if}
|
||||
{:catch err}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { noAuthAxios } from "@/lib/util/api";
|
||||
import { onMount } from "svelte";
|
||||
import { notify, unNotify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let error: string | undefined = $state();
|
||||
let login = $state(true);
|
||||
@@ -18,7 +19,7 @@
|
||||
|
||||
onMount(() => {
|
||||
if (localStorage.getItem("token")) {
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
}
|
||||
|
||||
if (!error && page.url.searchParams.get("again")) {
|
||||
@@ -35,7 +36,7 @@
|
||||
if (r?.data) {
|
||||
if (r.data.isInSetup) {
|
||||
console.log("Server is in setup.. navigating to web setup page.");
|
||||
goto("/setup");
|
||||
goto(toBaseUrl("/setup"));
|
||||
}
|
||||
availableProviders = r.data.available;
|
||||
apHeader = availableProviders?.includes("header");
|
||||
@@ -83,7 +84,7 @@
|
||||
} else {
|
||||
localStorage.removeItem("useEmby");
|
||||
}
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome ${user}!`, type: "success" });
|
||||
}
|
||||
})
|
||||
@@ -120,7 +121,7 @@
|
||||
if (resp.data?.token) {
|
||||
console.log("Received token... logging in.");
|
||||
localStorage.setItem("token", resp.data.token);
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome!`, type: "success" });
|
||||
}
|
||||
})
|
||||
@@ -148,7 +149,7 @@
|
||||
if (resp.data?.token) {
|
||||
console.log("Received token... logging in.");
|
||||
localStorage.setItem("token", resp.data.token);
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome!`, type: "success" });
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault } from "svelte/legacy";
|
||||
|
||||
import { goto } from "$app/navigation";
|
||||
import type { AvailableAuthProviders } from "@/types";
|
||||
import { noAuthAxios } from "@/lib/util/api";
|
||||
import { onMount } from "svelte";
|
||||
import { notify, unNotify } from "@/lib/util/notify";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
|
||||
let error: string = $state();
|
||||
let error: string | undefined = $state();
|
||||
|
||||
onMount(() => {
|
||||
if (localStorage.getItem("token")) {
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
}
|
||||
|
||||
noAuthAxios.get<AvailableAuthProviders>("/auth/available").then((r) => {
|
||||
if (r?.data) {
|
||||
if (!r?.data?.isInSetup) {
|
||||
console.log("Server not in setup.. navigating to login page.");
|
||||
goto("/login");
|
||||
goto(toBaseUrl("/login"));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function handleLogin(ev: SubmitEvent) {
|
||||
ev.preventDefault();
|
||||
const fd = new FormData(ev.target! as HTMLFormElement);
|
||||
const user = fd.get("username");
|
||||
const pass = fd.get("password");
|
||||
@@ -44,7 +44,7 @@
|
||||
if (resp.data?.token) {
|
||||
console.log("Received token... logging in.");
|
||||
localStorage.setItem("token", resp.data.token);
|
||||
goto("/");
|
||||
goto(toBaseUrl("/"));
|
||||
notify({ id: nid, text: `Welcome ${user}!`, type: "success" });
|
||||
}
|
||||
})
|
||||
@@ -74,7 +74,7 @@
|
||||
<span class="error">{error}!</span>
|
||||
{/if}
|
||||
|
||||
<form onsubmit={preventDefault(handleLogin)}>
|
||||
<form onsubmit={handleLogin}>
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" placeholder="Username" />
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script>
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/state";
|
||||
import { toBaseUrl } from "@/lib/util/url";
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@@ -16,7 +17,7 @@
|
||||
<h4 class="norm">We couldn't load this page</h4>
|
||||
{/if}
|
||||
<div class="btns">
|
||||
<button onclick={() => goto("/")}>Home</button>
|
||||
<button onclick={() => goto(toBaseUrl("/"))}>Home</button>
|
||||
<button onclick={() => location.reload()}>Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+5
-1
@@ -57,7 +57,11 @@ export type Icon =
|
||||
| "sparkles"
|
||||
| "tag"
|
||||
| "ticket"
|
||||
| "lock-closed";
|
||||
| "lock-closed"
|
||||
| "github"
|
||||
| "website"
|
||||
| "tmdb"
|
||||
| "igdb";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
|
||||
@@ -2,6 +2,14 @@ import adapter from "@sveltejs/adapter-node";
|
||||
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
|
||||
import { sveltePreprocess } from "svelte-preprocess";
|
||||
|
||||
if (
|
||||
process.env.WATCHARR_BASE &&
|
||||
(!process.env.WATCHARR_BASE.startsWith("/") ||
|
||||
process.env.WATCHARR_BASE.endsWith("/"))
|
||||
) {
|
||||
throw new Error("WATCHARR_BASE must start with, but not end with '/'");
|
||||
}
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
|
||||
@@ -21,6 +29,11 @@ const config = {
|
||||
alias: {
|
||||
"@": "src",
|
||||
},
|
||||
|
||||
paths: {
|
||||
base: process.env.WATCHARR_BASE,
|
||||
relative: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user