mirror of
https://github.com/maxdorninger/MediaManager.git
synced 2026-04-17 21:54:00 +02:00
Merge branch 'refs/heads/master' into fork/aasmoe/feat/multi-language-metadata
# Conflicts: # metadata_relay/app/tmdb.py # web/src/lib/api/api.d.ts
This commit is contained in:
@@ -7,6 +7,8 @@ Frontend settings are configured through environment variables in your `docker-c
|
||||
|
||||
## Configuration File Location
|
||||
|
||||
<warning>Note that MediaManager may need to be restarted for changes in the config file to take effect.</warning>
|
||||
|
||||
Your `config.toml` file should be in the directory that's mounted to `/app/config/config.toml` inside the container:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -36,6 +36,13 @@ DEBUG - media_manager.indexer.utils -
|
||||
Read timed out. (read timeout=10)
|
||||
```
|
||||
|
||||
- `follow_redirects`
|
||||
|
||||
This is necessary for some indexers that use redirect links for torrent downloads. Especially useful if your download
|
||||
client cannot access Prowlarr directly. This increases the time it takes to fetch torrent details, so only enable it if
|
||||
you really need it.
|
||||
Default is `false`.
|
||||
|
||||
## Jackett (`[indexers.jackett]`)
|
||||
|
||||
- `enabled`
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
Always check the container and browser logs for more specific error messages
|
||||
</tip>
|
||||
|
||||
## Authentication Issues
|
||||
|
||||
<procedure title="I can't log in with OAuth/OIDC?" id="procedure-i-cannot-log-in-with-oauth">
|
||||
<step>Verify your OAuth provider's configuration. <a href="authentication-setup.md" anchor="openid-connect-settings-auth-openid-connect">See the OAuth documentation</a></step>
|
||||
<step>Check if the callback URI you set in your OIDC providers settings is correct. <a href="authentication-setup.md" anchor="redirect-uri">See the callback URI documentation</a> </step>
|
||||
@@ -20,9 +22,15 @@
|
||||
</step>
|
||||
</procedure>
|
||||
|
||||
<procedure title="My hardlinks don't work?" id="procedure-my-hardlinks-dont-work">
|
||||
<step>Make sure you are using only one volumes for TV, Movies and Downloads. <a href="https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/docker-compose.yaml"> See the configuration in the example <code>docker-compose.yaml</code> file.</a></step>
|
||||
</procedure>
|
||||
## Hard linking Issues
|
||||
|
||||
- Make sure you are using only one volumes for TV, Movies and Downloads.
|
||||
<a href="https://raw.githubusercontent.com/maxdorninger/MediaManager/refs/heads/master/docker-compose.yaml"> See the configuration in the example <code>docker-compose.yaml</code> file.</a>
|
||||
|
||||
The reason is that hard linking only works within the same filesystem. If your downloads are on a different volume than
|
||||
your media library, hard linking will not work.
|
||||
|
||||
## Torrent Search Issues
|
||||
|
||||
<procedure title="I get no search results for torrents?" id="procedure-i-get-no-search-results">
|
||||
<step>Try switching to the advanced tab when searching for torrents.</step>
|
||||
@@ -30,4 +38,9 @@
|
||||
<step>If you still don't get any search results, check the logs, they will provide more information on what is going wrong.</step>
|
||||
</procedure>
|
||||
|
||||
## Import and download Issues
|
||||
|
||||
- If you configured a category with a special save path,
|
||||
<a href="qBittorrent-Category.md">carefully read this page about MM with qBittorrent save paths.</a>
|
||||
|
||||
<note>If it still doesn't work, <a href="https://github.com/maxdorninger/MediaManager/issues">please open an Issue.</a> It is possible that a bug is causing the issue.</note>
|
||||
@@ -121,6 +121,7 @@ url = "http://localhost:9696"
|
||||
api_key = ""
|
||||
reject_torrents_on_url_error = true
|
||||
timeout_seconds = 60
|
||||
follow_redirects = false
|
||||
|
||||
# Jackett settings
|
||||
[indexers.jackett]
|
||||
|
||||
@@ -121,6 +121,7 @@ url = "http://localhost:9696"
|
||||
api_key = ""
|
||||
reject_torrents_on_url_error = true
|
||||
timeout_seconds = 60
|
||||
follow_redirects = false
|
||||
|
||||
# Jackett settings
|
||||
[indexers.jackett]
|
||||
|
||||
@@ -7,6 +7,7 @@ class ProwlarrConfig(BaseSettings):
|
||||
url: str = "http://localhost:9696"
|
||||
reject_torrents_on_url_error: bool = True
|
||||
timeout_seconds: int = 60
|
||||
follow_redirects: bool = False
|
||||
|
||||
|
||||
class JackettConfig(BaseSettings):
|
||||
|
||||
@@ -27,6 +27,7 @@ class Prowlarr(GenericIndexer):
|
||||
self.url = config.url
|
||||
self.reject_torrents_on_url_error = config.reject_torrents_on_url_error
|
||||
self.timeout_seconds = config.timeout_seconds
|
||||
self.follow_redirects = config.follow_redirects
|
||||
|
||||
def search(self, query: str, is_tv: bool) -> list[IndexerQueryResult]:
|
||||
log.debug("Searching for " + query)
|
||||
@@ -94,7 +95,7 @@ class Prowlarr(GenericIndexer):
|
||||
log.error(f"No valid download URL found for result: {result}")
|
||||
return None
|
||||
|
||||
if not initial_url.startswith("magnet:"):
|
||||
if not initial_url.startswith("magnet:") and self.follow_redirects:
|
||||
try:
|
||||
final_download_url = follow_redirects_to_final_torrent_url(
|
||||
initial_url=initial_url,
|
||||
|
||||
@@ -28,6 +28,7 @@ from media_manager.movies.schemas import (
|
||||
)
|
||||
from media_manager.movies.dependencies import (
|
||||
movie_service_dep,
|
||||
movie_dep,
|
||||
)
|
||||
from media_manager.metadataProvider.dependencies import metadata_provider_dep
|
||||
from media_manager.movies.schemas import MovieRequestBase
|
||||
@@ -70,6 +71,24 @@ def add_a_movie(
|
||||
return movie
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{movie_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(current_superuser)],
|
||||
)
|
||||
def delete_a_movie(
|
||||
movie_service: movie_service_dep,
|
||||
movie: movie_dep,
|
||||
delete_files_on_disk: bool = False,
|
||||
delete_torrents: bool = False,
|
||||
):
|
||||
movie_service.delete_movie(
|
||||
movie_id=movie.id,
|
||||
delete_files_on_disk=delete_files_on_disk,
|
||||
delete_torrents=delete_torrents,
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------
|
||||
# GET MOVIES
|
||||
# --------------------------------
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
@@ -114,6 +115,48 @@ class MovieService:
|
||||
"""
|
||||
self.movie_repository.delete_movie_request(movie_request_id=movie_request_id)
|
||||
|
||||
def delete_movie(
|
||||
self,
|
||||
movie_id: MovieId,
|
||||
delete_files_on_disk: bool = False,
|
||||
delete_torrents: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Delete a movie from the database, optionally deleting files and torrents.
|
||||
|
||||
:param movie_id: The ID of the movie to delete.
|
||||
:param delete_files_on_disk: Whether to delete the movie's files from disk.
|
||||
:param delete_torrents: Whether to delete associated torrents from the torrent client.
|
||||
"""
|
||||
if delete_files_on_disk or delete_torrents:
|
||||
movie = self.movie_repository.get_movie_by_id(movie_id=movie_id)
|
||||
|
||||
log.debug(f"Deleting ID: {movie.id} - Name: {movie.name}")
|
||||
|
||||
if delete_files_on_disk:
|
||||
# Get the movie's directory path
|
||||
movie_dir = self.get_movie_root_path(movie=movie)
|
||||
|
||||
log.debug(f"Attempt to delete movie directory: {movie_dir}")
|
||||
if movie_dir.exists() and movie_dir.is_dir():
|
||||
shutil.rmtree(movie_dir)
|
||||
log.info(f"Deleted movie directory: {movie_dir}")
|
||||
|
||||
if delete_torrents:
|
||||
# Get all torrents associated with this movie
|
||||
torrents = self.movie_repository.get_torrents_by_movie_id(
|
||||
movie_id=movie_id
|
||||
)
|
||||
for torrent in torrents:
|
||||
try:
|
||||
self.torrent_service.cancel_download(torrent, delete_files=True)
|
||||
log.info(f"Deleted torrent: {torrent.hash}")
|
||||
except Exception as e:
|
||||
log.warning(f"Failed to delete torrent {torrent.hash}: {e}")
|
||||
|
||||
# Delete from database
|
||||
self.movie_repository.delete_movie(movie_id=movie_id)
|
||||
|
||||
def get_public_movie_files_by_movie_id(
|
||||
self, movie_id: MovieId
|
||||
) -> list[PublicMovieFile]:
|
||||
@@ -233,11 +276,14 @@ class MovieService:
|
||||
# Fetch the internal movie ID.
|
||||
try:
|
||||
movie = self.movie_repository.get_movie_by_external_id(
|
||||
external_id=result.external_id, metadata_provider=metadata_provider.name
|
||||
external_id=result.external_id,
|
||||
metadata_provider=metadata_provider.name,
|
||||
)
|
||||
result.id = movie.id
|
||||
except Exception:
|
||||
log.error(f"Unable to find internal movie ID for {result.external_id} on {metadata_provider.name}")
|
||||
log.error(
|
||||
f"Unable to find internal movie ID for {result.external_id} on {metadata_provider.name}"
|
||||
)
|
||||
return results
|
||||
|
||||
def get_popular_movies(
|
||||
|
||||
@@ -9,8 +9,11 @@ import bencoder
|
||||
import patoolib
|
||||
import requests
|
||||
import libtorrent
|
||||
from requests.exceptions import InvalidSchema
|
||||
|
||||
from media_manager.config import AllEncompassingConfig
|
||||
from media_manager.indexer.schemas import IndexerQueryResult
|
||||
from media_manager.indexer.utils import follow_redirects_to_final_torrent_url
|
||||
from media_manager.torrent.schemas import Torrent
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -141,6 +144,15 @@ def get_torrent_hash(torrent: IndexerQueryResult) -> str:
|
||||
response = requests.get(str(torrent.download_url), timeout=30)
|
||||
response.raise_for_status()
|
||||
torrent_content = response.content
|
||||
except InvalidSchema as e:
|
||||
log.debug(f"Invalid schema for URL {torrent.download_url}: {e}")
|
||||
final_url = follow_redirects_to_final_torrent_url(
|
||||
initial_url=torrent.download_url,
|
||||
session=requests.Session(),
|
||||
timeout=AllEncompassingConfig().indexers.prowlarr.timeout_seconds,
|
||||
)
|
||||
torrent_hash = str(libtorrent.parse_magnet_uri(final_url).info_hash)
|
||||
return torrent_hash
|
||||
except Exception as e:
|
||||
log.error(f"Failed to download torrent file: {e}")
|
||||
raise
|
||||
|
||||
@@ -34,7 +34,6 @@ from media_manager.schemas import MediaImportSuggestion
|
||||
from media_manager.tv.dependencies import (
|
||||
season_dep,
|
||||
show_dep,
|
||||
tv_repository_dep,
|
||||
tv_service_dep,
|
||||
)
|
||||
from media_manager.metadataProvider.dependencies import metadata_provider_dep
|
||||
@@ -88,10 +87,19 @@ def get_total_count_of_downloaded_episodes(tv_service: tv_service_dep):
|
||||
@router.delete(
|
||||
"/shows/{show_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
dependencies=[Depends(current_active_user)],
|
||||
dependencies=[Depends(current_superuser)],
|
||||
)
|
||||
def delete_a_show(tv_repository: tv_repository_dep, show: show_dep):
|
||||
tv_repository.delete_show(show_id=show.id)
|
||||
def delete_a_show(
|
||||
tv_service: tv_service_dep,
|
||||
show: show_dep,
|
||||
delete_files_on_disk: bool = False,
|
||||
delete_torrents: bool = False,
|
||||
):
|
||||
tv_service.delete_show(
|
||||
show_id=show.id,
|
||||
delete_files_on_disk=delete_files_on_disk,
|
||||
delete_torrents=delete_torrents,
|
||||
)
|
||||
|
||||
|
||||
# --------------------------------
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
@@ -130,6 +131,46 @@ class TvService:
|
||||
"""
|
||||
self.tv_repository.delete_season_request(season_request_id=season_request_id)
|
||||
|
||||
def delete_show(
|
||||
self,
|
||||
show_id: ShowId,
|
||||
delete_files_on_disk: bool = False,
|
||||
delete_torrents: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Delete a show from the database, optionally deleting files and torrents.
|
||||
|
||||
:param show_id: The ID of the show to delete.
|
||||
:param delete_files_on_disk: Whether to delete the show's files from disk.
|
||||
:param delete_torrents: Whether to delete associated torrents from the torrent client.
|
||||
"""
|
||||
if delete_files_on_disk or delete_torrents:
|
||||
show = self.tv_repository.get_show_by_id(show_id)
|
||||
|
||||
log.debug(f"Deleting ID: {show.id} - Name: {show.name}")
|
||||
|
||||
if delete_files_on_disk:
|
||||
# Get the show's directory path
|
||||
show_dir = self.get_root_show_directory(show=show)
|
||||
|
||||
log.debug(f"Attempt to delete show directory: {show_dir}")
|
||||
if show_dir.exists() and show_dir.is_dir():
|
||||
shutil.rmtree(show_dir)
|
||||
log.info(f"Deleted show directory: {show_dir}")
|
||||
|
||||
if delete_torrents:
|
||||
# Get all torrents associated with this show
|
||||
torrents = self.tv_repository.get_torrents_by_show_id(show_id=show_id)
|
||||
for torrent in torrents:
|
||||
try:
|
||||
self.torrent_service.cancel_download(torrent, delete_files=True)
|
||||
log.info(f"Deleted torrent: {torrent.hash}")
|
||||
except Exception as e:
|
||||
log.warning(f"Failed to delete torrent {torrent.hash}: {e}")
|
||||
|
||||
# Delete from database
|
||||
self.tv_repository.delete_show(show_id=show_id)
|
||||
|
||||
def get_public_season_files_by_season_id(
|
||||
self, season_id: SeasonId
|
||||
) -> list[PublicSeasonFile]:
|
||||
@@ -246,11 +287,14 @@ class TvService:
|
||||
# Fetch the internal show ID.
|
||||
try:
|
||||
show = self.tv_repository.get_show_by_external_id(
|
||||
external_id=result.external_id, metadata_provider=metadata_provider.name
|
||||
external_id=result.external_id,
|
||||
metadata_provider=metadata_provider.name,
|
||||
)
|
||||
result.id = show.id
|
||||
except Exception:
|
||||
log.error(f"Unable to find internal show ID for {result.external_id} on {metadata_provider.name}")
|
||||
log.error(
|
||||
f"Unable to find internal show ID for {result.external_id} on {metadata_provider.name}"
|
||||
)
|
||||
return results
|
||||
|
||||
def get_popular_shows(
|
||||
|
||||
@@ -21,7 +21,7 @@ else:
|
||||
|
||||
@router.get("/tv/search")
|
||||
async def search_tmdb_tv(query: str, page: int = 1, language: str = "en"):
|
||||
return Search().tv(page=page, query=query, include_adult=True, language=language)
|
||||
return Search().tv(page=page, query=query, language=language)
|
||||
|
||||
@router.get("/tv/shows/{show_id}")
|
||||
async def get_tmdb_show(show_id: int, language: str = "en"):
|
||||
@@ -37,7 +37,7 @@ else:
|
||||
|
||||
@router.get("/movies/search")
|
||||
async def search_tmdb_movies(query: str, page: int = 1, language: str = "en"):
|
||||
return Search().movie(page=page, query=query, include_adult=True, language=language)
|
||||
return Search().movie(page=page, query=query, language=language)
|
||||
|
||||
@router.get("/movies/{movie_id}")
|
||||
async def get_tmdb_movie(movie_id: int, language: str = "en"):
|
||||
|
||||
109
web/src/lib/components/delete-media-dialog.svelte
Normal file
109
web/src/lib/components/delete-media-dialog.svelte
Normal file
@@ -0,0 +1,109 @@
|
||||
<script lang="ts">
|
||||
import type { components } from '$lib/api/api.ts';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import client from '$lib/api/index.ts';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { getFullyQualifiedMediaName } from '$lib/utils.ts';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog/index.js';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox/index.js';
|
||||
import { Label } from '$lib/components/ui/label/index.js';
|
||||
import { buttonVariants } from '$lib/components/ui/button/index.js';
|
||||
|
||||
let {
|
||||
media,
|
||||
isShow
|
||||
}: {
|
||||
media: components['schemas']['PublicMovie'] | components['schemas']['PublicShow'];
|
||||
isShow: boolean;
|
||||
} = $props();
|
||||
let deleteDialogOpen = $state(false);
|
||||
let deleteFilesOnDisk = $state(false);
|
||||
let deleteTorrents = $state(false);
|
||||
|
||||
async function delete_movie() {
|
||||
if (!media.id) {
|
||||
toast.error('Movie ID is missing');
|
||||
return;
|
||||
}
|
||||
const { response } = await client.DELETE('/api/v1/movies/{movie_id}', {
|
||||
params: {
|
||||
path: { movie_id: media.id },
|
||||
query: { delete_files_on_disk: deleteFilesOnDisk, delete_torrents: deleteTorrents }
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
toast.error('Failed to delete movie: ' + errorText);
|
||||
} else {
|
||||
toast.success('Movie deleted successfully.');
|
||||
deleteDialogOpen = false;
|
||||
await goto(resolve('/dashboard/movies', {}), { invalidateAll: true });
|
||||
}
|
||||
}
|
||||
|
||||
async function delete_show() {
|
||||
const { response } = await client.DELETE('/api/v1/tv/shows/{show_id}', {
|
||||
params: {
|
||||
path: { show_id: media.id! },
|
||||
query: { delete_files_on_disk: deleteFilesOnDisk, delete_torrents: deleteTorrents }
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
toast.error('Failed to delete show: ' + errorText);
|
||||
} else {
|
||||
toast.success('Show deleted successfully.');
|
||||
deleteDialogOpen = false;
|
||||
await goto(resolve('/dashboard/tv', {}), { invalidateAll: true });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<AlertDialog.Root bind:open={deleteDialogOpen}>
|
||||
<AlertDialog.Trigger class={buttonVariants({ variant: 'destructive' })}>
|
||||
Delete {isShow ? ' Show' : ' Movie'}
|
||||
</AlertDialog.Trigger>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
<AlertDialog.Title>Delete - {getFullyQualifiedMediaName(media)}?</AlertDialog.Title>
|
||||
<AlertDialog.Description>
|
||||
This action cannot be undone. This will permanently delete
|
||||
<strong>{getFullyQualifiedMediaName(media)}</strong>.
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
<div class="flex flex-col gap-3 py-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox bind:checked={deleteFilesOnDisk} id="delete-files" />
|
||||
<Label
|
||||
for="delete-files"
|
||||
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Also delete files on disk (this will only remove imported files, not downloads)
|
||||
</Label>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<Checkbox bind:checked={deleteTorrents} id="delete-torrents" />
|
||||
<Label
|
||||
for="delete-torrents"
|
||||
class="text-sm leading-none font-medium peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
Also delete torrents (this will remove torrents from your download clients)
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
<AlertDialog.Footer>
|
||||
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
||||
<AlertDialog.Action
|
||||
onclick={() => {
|
||||
if (isShow) {
|
||||
delete_show();
|
||||
} else delete_movie();
|
||||
}}
|
||||
class={buttonVariants({ variant: 'destructive' })}
|
||||
>
|
||||
Delete
|
||||
</AlertDialog.Action>
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
@@ -22,6 +22,7 @@
|
||||
let email = $state('');
|
||||
let password = $state('');
|
||||
let errorMessage = $state('');
|
||||
let successMessage = $state('');
|
||||
let isLoading = $state(false);
|
||||
|
||||
async function handleLogin(event: Event) {
|
||||
@@ -29,6 +30,7 @@
|
||||
|
||||
isLoading = true;
|
||||
errorMessage = '';
|
||||
successMessage = '';
|
||||
|
||||
const { error, response } = await client.POST('/api/v1/auth/cookie/login', {
|
||||
body: {
|
||||
@@ -45,8 +47,8 @@
|
||||
if (!error) {
|
||||
console.log('Login successful!');
|
||||
console.log('Received User Data: ', response);
|
||||
errorMessage = 'Login successful! Redirecting...';
|
||||
toast.success(errorMessage);
|
||||
successMessage = 'Login successful! Redirecting...';
|
||||
toast.success(successMessage);
|
||||
goto(resolve('/dashboard', {}));
|
||||
} else {
|
||||
toast.error('Login failed!');
|
||||
@@ -100,6 +102,13 @@
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
|
||||
{#if successMessage}
|
||||
<Alert.Root variant="default">
|
||||
<Alert.Title>Success</Alert.Title>
|
||||
<Alert.Description>{successMessage}</Alert.Description>
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
|
||||
{#if isLoading}
|
||||
<LoadingBar />
|
||||
{/if}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
import LibraryCombobox from '$lib/components/library-combobox.svelte';
|
||||
import { base } from '$app/paths';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import DeleteMediaDialog from '$lib/components/delete-media-dialog.svelte';
|
||||
|
||||
let movie: components['schemas']['PublicMovie'] = page.data.movie;
|
||||
let user: () => components['schemas']['UserRead'] = getContext('user');
|
||||
@@ -92,6 +93,7 @@
|
||||
</Card.Header>
|
||||
<Card.Content class="flex flex-col items-center gap-4">
|
||||
<LibraryCombobox media={movie} mediaType="movie" />
|
||||
<DeleteMediaDialog isShow={false} media={movie} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import LibraryCombobox from '$lib/components/library-combobox.svelte';
|
||||
import * as Card from '$lib/components/ui/card/index.js';
|
||||
import DeleteMediaDialog from '$lib/components/delete-media-dialog.svelte';
|
||||
import { resolve } from '$app/paths';
|
||||
import client from '$lib/api';
|
||||
|
||||
@@ -135,6 +136,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
<LibraryCombobox media={show()} mediaType="tv" />
|
||||
<DeleteMediaDialog isShow={true} media={show()} />
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user