refactor(metadata): rewrite metadata settings
This commit is contained in:
@@ -533,18 +533,6 @@ components:
|
||||
type: string
|
||||
enum: [tvdb, tmdb]
|
||||
example: 'tvdb'
|
||||
providers:
|
||||
type: object
|
||||
properties:
|
||||
tvdb:
|
||||
type: object
|
||||
properties:
|
||||
apiKey:
|
||||
type: string
|
||||
example: '123456789'
|
||||
pin:
|
||||
type: string
|
||||
example: '1234'
|
||||
TautulliSettings:
|
||||
type: object
|
||||
properties:
|
||||
@@ -2631,6 +2619,19 @@ paths:
|
||||
description: Tests if the TVDB configuration is valid. Returns a list of available languages on success.
|
||||
tags:
|
||||
- settings
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
tmdb:
|
||||
type: boolean
|
||||
example: true
|
||||
tvdb:
|
||||
type: boolean
|
||||
example: true
|
||||
responses:
|
||||
'200':
|
||||
description: Succesfully connected to TVDB
|
||||
|
||||
@@ -10,17 +10,17 @@ export const getMetadataProvider = async (
|
||||
try {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.tvdb.apiKey || mediaType == 'movie') {
|
||||
if (mediaType == 'movie') {
|
||||
return new TheMovieDb();
|
||||
}
|
||||
|
||||
if (mediaType == 'tv' && settings.metadataType.tv == IndexerType.TVDB) {
|
||||
if (mediaType == 'tv' && settings.metadataSettings.tv == IndexerType.TVDB) {
|
||||
return await Tvdb.getInstance();
|
||||
}
|
||||
|
||||
if (
|
||||
mediaType == 'anime' &&
|
||||
settings.metadataType.anime == IndexerType.TVDB
|
||||
settings.metadataSettings.anime == IndexerType.TVDB
|
||||
) {
|
||||
return await Tvdb.getInstance();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import type {
|
||||
TvdbTvDetails,
|
||||
} from '@server/api/tvdb/interfaces';
|
||||
import cacheManager, { type AvailableCacheIds } from '@server/lib/cache';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
|
||||
interface TvdbConfig {
|
||||
@@ -45,12 +44,12 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
private apiKey?: string;
|
||||
private pin?: string;
|
||||
|
||||
constructor(apiKey: string, pin?: string) {
|
||||
constructor(pin?: string) {
|
||||
const finalConfig = { ...DEFAULT_CONFIG };
|
||||
super(
|
||||
finalConfig.baseUrl,
|
||||
{
|
||||
apiKey: apiKey,
|
||||
apiKey: '',
|
||||
},
|
||||
{
|
||||
nodeCache: cacheManager.getCache(finalConfig.cachePrefix).data,
|
||||
@@ -60,28 +59,19 @@ class Tvdb extends ExternalAPI implements TvShowIndexer {
|
||||
},
|
||||
}
|
||||
);
|
||||
this.apiKey = apiKey;
|
||||
this.pin = pin;
|
||||
this.tmdb = new TheMovieDb();
|
||||
}
|
||||
|
||||
public static async getInstance(): Promise<Tvdb> {
|
||||
if (!this.instance) {
|
||||
const settings = await getSettings();
|
||||
|
||||
if (!settings.tvdb.apiKey) {
|
||||
throw new Error('TVDB API key is not set');
|
||||
}
|
||||
|
||||
try {
|
||||
this.instance = new Tvdb(settings.tvdb.apiKey, settings.tvdb.pin);
|
||||
this.instance = new Tvdb();
|
||||
await this.instance.login();
|
||||
} catch (error) {
|
||||
logger.error(`Failed to login to TVDB: ${error.message}`);
|
||||
throw new Error('TVDB API key is not set');
|
||||
}
|
||||
|
||||
this.instance = new Tvdb(settings.tvdb.apiKey, settings.tvdb.pin);
|
||||
}
|
||||
|
||||
return this.instance;
|
||||
|
||||
@@ -101,24 +101,11 @@ interface Quota {
|
||||
}
|
||||
|
||||
export enum IndexerType {
|
||||
TMDB,
|
||||
TVDB,
|
||||
TMDB = 'tmdb',
|
||||
TVDB = 'tvdb',
|
||||
}
|
||||
|
||||
export interface MetadataSettings {
|
||||
settings: MetadataTypeSettings;
|
||||
providers: ProviderSettings;
|
||||
}
|
||||
|
||||
export interface TvdbSettings {
|
||||
apiKey?: string;
|
||||
pin?: string;
|
||||
}
|
||||
|
||||
export interface ProviderSettings {
|
||||
tvdb: TvdbSettings;
|
||||
}
|
||||
export interface MetadataTypeSettings {
|
||||
tv: IndexerType;
|
||||
anime: IndexerType;
|
||||
}
|
||||
@@ -373,7 +360,6 @@ export interface AllSettings {
|
||||
notifications: NotificationSettings;
|
||||
jobs: Record<JobId, JobSettings>;
|
||||
network: NetworkSettings;
|
||||
tvdb: TvdbSettings;
|
||||
metadataSettings: MetadataSettings;
|
||||
}
|
||||
|
||||
@@ -436,20 +422,8 @@ class Settings {
|
||||
},
|
||||
tautulli: {},
|
||||
metadataSettings: {
|
||||
settings: {
|
||||
tv: IndexerType.TMDB,
|
||||
anime: IndexerType.TVDB,
|
||||
},
|
||||
providers: {
|
||||
tvdb: {
|
||||
apiKey: '',
|
||||
pin: '',
|
||||
},
|
||||
},
|
||||
},
|
||||
tvdb: {
|
||||
apiKey: '',
|
||||
pin: '',
|
||||
tv: IndexerType.TMDB,
|
||||
anime: IndexerType.TVDB,
|
||||
},
|
||||
radarr: [],
|
||||
sonarr: [],
|
||||
@@ -653,22 +627,6 @@ class Settings {
|
||||
this.data.metadataSettings = data;
|
||||
}
|
||||
|
||||
get tvdb(): TvdbSettings {
|
||||
return this.data.tvdb;
|
||||
}
|
||||
|
||||
set tvdb(data: TvdbSettings) {
|
||||
this.data.tvdb = data;
|
||||
}
|
||||
|
||||
get metadataType(): MetadataTypeSettings {
|
||||
return this.data.metadataSettings.settings;
|
||||
}
|
||||
|
||||
set metadataType(data: MetadataTypeSettings) {
|
||||
this.data.metadataSettings.settings = data;
|
||||
}
|
||||
|
||||
get radarr(): RadarrSettings[] {
|
||||
return this.data.radarr;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import TheMovieDb from '@server/api/themoviedb';
|
||||
import Tvdb from '@server/api/tvdb';
|
||||
import { getSettings, type MetadataSettings } from '@server/lib/settings';
|
||||
import {
|
||||
getSettings,
|
||||
IndexerType,
|
||||
type MetadataSettings,
|
||||
} from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { Router } from 'express';
|
||||
|
||||
@@ -8,39 +13,137 @@ const metadataRoutes = Router();
|
||||
metadataRoutes.get('/', (_req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
res.status(200).json(settings.metadataSettings);
|
||||
res.status(200).json({
|
||||
metadata: {
|
||||
tv: settings.metadataSettings.tv === IndexerType.TMDB ? 'tmdb' : 'tvdb',
|
||||
anime:
|
||||
settings.metadataSettings.anime === IndexerType.TMDB ? 'tmdb' : 'tvdb',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
metadataRoutes.put('/', (req, res) => {
|
||||
metadataRoutes.put('/', async (req, res) => {
|
||||
const settings = getSettings();
|
||||
|
||||
const body = req.body as MetadataSettings;
|
||||
|
||||
settings.metadataSettings = {
|
||||
providers: body.providers,
|
||||
settings: body.settings,
|
||||
};
|
||||
settings.save();
|
||||
// test indexers
|
||||
let tvdbTest = -1;
|
||||
let tmdbTest = -1;
|
||||
|
||||
return res.status(200).json({
|
||||
tvdb: settings.tvdb,
|
||||
try {
|
||||
if (body.tv === IndexerType.TVDB || body.anime === IndexerType.TVDB) {
|
||||
tvdbTest = 0;
|
||||
const tvdb = await Tvdb.getInstance();
|
||||
await tvdb.test();
|
||||
tvdbTest = 1;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to test indexers', {
|
||||
label: 'Metadata',
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (body.tv === IndexerType.TMDB || body.anime === IndexerType.TMDB) {
|
||||
tmdbTest = 0;
|
||||
const tmdb = new TheMovieDb();
|
||||
await tmdb.getTvShow({ tvId: 1054 });
|
||||
tmdbTest = 1;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to test indexers', {
|
||||
label: 'Metadata',
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('Updated metadata settings', {
|
||||
label: 'Metadata',
|
||||
body: body,
|
||||
tmdb: tmdbTest,
|
||||
tvdb: tvdbTest,
|
||||
tv: body.tv,
|
||||
anime: body.anime,
|
||||
});
|
||||
|
||||
if (tvdbTest === 0 || tmdbTest === 0) {
|
||||
return res.status(500).json({
|
||||
tvdb: tvdbTest === 1 ? 'ok' : 'failed',
|
||||
tmdb: tmdbTest === 1 ? 'ok' : 'failed',
|
||||
});
|
||||
}
|
||||
|
||||
settings.metadataSettings = {
|
||||
tv: body.tv,
|
||||
anime: body.anime,
|
||||
};
|
||||
await settings.save();
|
||||
|
||||
res.status(200).json({
|
||||
tv: settings.metadataSettings.tv === IndexerType.TMDB ? 'tmdb' : 'tvdb',
|
||||
anime:
|
||||
settings.metadataSettings.anime === IndexerType.TMDB ? 'tmdb' : 'tvdb',
|
||||
});
|
||||
});
|
||||
|
||||
metadataRoutes.post('/test', async (req, res, next) => {
|
||||
try {
|
||||
const tvdb = await Tvdb.getInstance();
|
||||
await tvdb.test();
|
||||
metadataRoutes.post('/test', async (req, res) => {
|
||||
let tvdbTest = -1;
|
||||
let tmdbTest = -1;
|
||||
|
||||
// TODO: add tmdb test
|
||||
return res.status(200).json({ tvdb: true });
|
||||
try {
|
||||
const body = req.body as { tmdb: boolean; tvdb: boolean };
|
||||
|
||||
try {
|
||||
if (body.tmdb) {
|
||||
tmdbTest = 0;
|
||||
const tmdb = new TheMovieDb();
|
||||
await tmdb.getTvShow({ tvId: 1054 });
|
||||
tmdbTest = 1;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to test indexers', {
|
||||
label: 'Metadata',
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
if (body.tvdb) {
|
||||
tvdbTest = 0;
|
||||
const tvdb = await Tvdb.getInstance();
|
||||
await tvdb.test();
|
||||
tvdbTest = 1;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('Failed to test indexers', {
|
||||
label: 'Metadata',
|
||||
message: e.message,
|
||||
});
|
||||
}
|
||||
|
||||
const response = {
|
||||
tmdb: tmdbTest === -1 ? 'not tested' : tmdbTest === 0 ? 'failed' : 'ok',
|
||||
tvdb: tvdbTest === -1 ? 'not tested' : tvdbTest === 0 ? 'failed' : 'ok',
|
||||
};
|
||||
|
||||
return res.status(200).json(response);
|
||||
} catch (e) {
|
||||
logger.error('Failed to test Tvdb', {
|
||||
label: 'Tvdb',
|
||||
logger.error('Failed to test indexers', {
|
||||
label: 'Metadata',
|
||||
message: e.message,
|
||||
});
|
||||
|
||||
return next({ status: 500, message: 'Failed to connect to Tvdb' });
|
||||
// if tmdbTest != -1 (tested) and tmdbTest === 1 (ok) then fail
|
||||
// if tvdbTest != -1 (tested) and tvdbTest === 1 (ok) then fail
|
||||
// if test === -1 = 'not tested' if test === 0 = 'failed' if test === 1 = 'ok'
|
||||
const response = {
|
||||
tmdb: tmdbTest === -1 ? 'not tested' : tmdbTest === 0 ? 'failed' : 'ok',
|
||||
tvdb: tvdbTest === -1 ? 'not tested' : tvdbTest === 0 ? 'failed' : 'ok',
|
||||
};
|
||||
|
||||
return res.status(500).json(response);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
94
src/components/MetadataSelector/index.tsx
Normal file
94
src/components/MetadataSelector/index.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import TmdbLogo from '@app/assets/services/tmdb.svg';
|
||||
import TvdbLogo from '@app/assets/services/tvdb.svg';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import React from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import Select, { type StylesConfig } from 'react-select';
|
||||
|
||||
enum IndexerType {
|
||||
TMDB = 'tmdb',
|
||||
TVDB = 'tvdb',
|
||||
}
|
||||
|
||||
type IndexerOptionType = {
|
||||
value: IndexerType;
|
||||
label: string;
|
||||
icon: React.ReactNode;
|
||||
};
|
||||
|
||||
const messages = defineMessages('components.MetadataSelector', {
|
||||
tmdbLabel: 'TMDB',
|
||||
tvdbLabel: 'TVDB',
|
||||
selectIndexer: 'Select an indexer',
|
||||
});
|
||||
|
||||
interface MetadataSelectorProps {
|
||||
value: IndexerType;
|
||||
onChange: (value: IndexerType) => void;
|
||||
isDisabled?: boolean;
|
||||
}
|
||||
|
||||
const MetadataSelector = ({
|
||||
value,
|
||||
onChange,
|
||||
isDisabled = false,
|
||||
}: MetadataSelectorProps) => {
|
||||
const intl = useIntl();
|
||||
|
||||
// Options pour les sélecteurs d'indexeurs avec leurs logos
|
||||
const indexerOptions: IndexerOptionType[] = [
|
||||
{
|
||||
value: IndexerType.TMDB,
|
||||
label: intl.formatMessage(messages.tmdbLabel),
|
||||
icon: <TmdbLogo />,
|
||||
},
|
||||
{
|
||||
value: IndexerType.TVDB,
|
||||
label: intl.formatMessage(messages.tvdbLabel),
|
||||
icon: <TvdbLogo />,
|
||||
},
|
||||
];
|
||||
|
||||
// Style personnalisé pour inclure les icônes
|
||||
const customStyles: StylesConfig<IndexerOptionType, false> = {
|
||||
option: (base) => ({
|
||||
...base,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
singleValue: (base) => ({
|
||||
...base,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}),
|
||||
};
|
||||
|
||||
// Format personnalisé pour les options avec logo
|
||||
const formatOptionLabel = (option: IndexerOptionType) => (
|
||||
<div className="flex items-center">
|
||||
{option.icon}
|
||||
<span>{option.label}</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
options={indexerOptions}
|
||||
isDisabled={isDisabled}
|
||||
className="react-select-container"
|
||||
classNamePrefix="react-select"
|
||||
value={indexerOptions.find((option) => option.value === value)}
|
||||
onChange={(selectedOption) => {
|
||||
if (selectedOption) {
|
||||
onChange(selectedOption.value);
|
||||
}
|
||||
}}
|
||||
placeholder={intl.formatMessage(messages.selectIndexer)}
|
||||
styles={customStyles}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { IndexerType };
|
||||
export default MetadataSelector;
|
||||
@@ -1,10 +1,14 @@
|
||||
import Badge from '@app/components/Common/Badge';
|
||||
import Button from '@app/components/Common/Button';
|
||||
import LoadingSpinner from '@app/components/Common/LoadingSpinner';
|
||||
import PageTitle from '@app/components/Common/PageTitle';
|
||||
import MetadataSelector, {
|
||||
IndexerType,
|
||||
} from '@app/components/MetadataSelector';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
import { ArrowDownOnSquareIcon, BeakerIcon } from '@heroicons/react/24/outline';
|
||||
import { Field, Form, Formik } from 'formik';
|
||||
import { Form, Formik } from 'formik';
|
||||
import { useState } from 'react';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { useToasts } from 'react-toast-notifications';
|
||||
@@ -13,91 +17,171 @@ import useSWR from 'swr';
|
||||
const messages = defineMessages('components.Settings', {
|
||||
general: 'General',
|
||||
settings: 'Settings',
|
||||
apiKey: 'Api Key',
|
||||
pin: 'Pin',
|
||||
enableTip:
|
||||
'Enable Tvdb (only for season and episode).' +
|
||||
' Due to a limitation of the api used, only English is available.',
|
||||
seriesIndexer: 'Series Indexer',
|
||||
animeIndexer: 'Anime Indexer',
|
||||
metadataSettings: 'Settings for metadata provider',
|
||||
clickTest: 'Click on the "Test" button to check connectivity with providers',
|
||||
notTested: 'Not Tested',
|
||||
failed: 'Error',
|
||||
ok: 'OK',
|
||||
providerStatus: 'Provider Status',
|
||||
chooseProvider: 'Choose metadata providers for different content types',
|
||||
indexerSelection: 'Provider Selection',
|
||||
});
|
||||
|
||||
interface providerResponse {
|
||||
tvdb: boolean;
|
||||
tmdb: boolean;
|
||||
// Types
|
||||
type ProviderStatus = 'ok' | 'not tested' | 'failed';
|
||||
|
||||
interface ProviderResponse {
|
||||
tvdb: ProviderStatus;
|
||||
tmdb: ProviderStatus;
|
||||
}
|
||||
|
||||
enum indexerType {
|
||||
TMDB,
|
||||
TVDB,
|
||||
interface MetadataValues {
|
||||
tv: IndexerType;
|
||||
anime: IndexerType;
|
||||
}
|
||||
|
||||
interface metadataSettings {
|
||||
settings: metadataTypeSettings;
|
||||
providers: providerSettings;
|
||||
}
|
||||
|
||||
interface metadataTypeSettings {
|
||||
tv: indexerType;
|
||||
anime: indexerType;
|
||||
}
|
||||
|
||||
interface providerSettings {
|
||||
tvdb: tvdbSettings;
|
||||
}
|
||||
|
||||
interface tvdbSettings {
|
||||
apiKey: string;
|
||||
pin: string;
|
||||
interface MetadataSettings {
|
||||
metadata: MetadataValues;
|
||||
}
|
||||
|
||||
const SettingsMetadata = () => {
|
||||
const intl = useIntl();
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
|
||||
const { addToast } = useToasts();
|
||||
const [isTesting, setIsTesting] = useState(false);
|
||||
// Valeurs par défaut pour les statuts
|
||||
const defaultStatus: ProviderResponse = {
|
||||
tmdb: 'not tested',
|
||||
tvdb: 'not tested',
|
||||
};
|
||||
|
||||
const [providerStatus, setProviderStatus] =
|
||||
useState<ProviderResponse>(defaultStatus);
|
||||
|
||||
// SWR hook pour récupérer les données
|
||||
const { data, error } = useSWR<MetadataSettings>(
|
||||
'/api/v1/settings/metadatas'
|
||||
);
|
||||
|
||||
// Tester la connexion avec les fournisseurs
|
||||
const testConnection = async (
|
||||
values: MetadataValues
|
||||
): Promise<ProviderResponse> => {
|
||||
// Déterminer quels indexeurs sont utilisés
|
||||
const useTmdb =
|
||||
values.tv === IndexerType.TMDB || values.anime === IndexerType.TMDB;
|
||||
const useTvdb =
|
||||
values.tv === IndexerType.TVDB || values.anime === IndexerType.TVDB;
|
||||
|
||||
// Préparer les données pour le test
|
||||
const testData = {
|
||||
tmdb: useTmdb,
|
||||
tvdb: useTvdb,
|
||||
};
|
||||
|
||||
const testConnection = async () => {
|
||||
const response = await fetch('/api/v1/settings/metadatas/test', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(testData),
|
||||
});
|
||||
|
||||
const body = (await response.json()) as providerResponse;
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to test Tvdb connection');
|
||||
throw new Error('Failed to test connection');
|
||||
}
|
||||
|
||||
console.log(body);
|
||||
const body = (await response.json()) as ProviderResponse;
|
||||
|
||||
// Créer un nouvel objet de statut en conservant 'not tested'
|
||||
// pour les services que nous n'avons pas testés
|
||||
const newStatus: ProviderResponse = {
|
||||
tmdb: useTmdb ? body.tmdb : 'not tested',
|
||||
tvdb: useTvdb ? body.tvdb : 'not tested',
|
||||
};
|
||||
|
||||
setProviderStatus(newStatus);
|
||||
return newStatus;
|
||||
};
|
||||
|
||||
// Sauvegarder les paramètres
|
||||
const saveSettings = async (
|
||||
value: metadataSettings
|
||||
): Promise<metadataSettings> => {
|
||||
values: MetadataValues
|
||||
): Promise<MetadataSettings> => {
|
||||
const response = await fetch('/api/v1/settings/metadatas', {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(value),
|
||||
body: JSON.stringify({
|
||||
anime: values.anime,
|
||||
tv: values.tv,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save Metadata settings');
|
||||
}
|
||||
|
||||
return (await response.json()) as metadataSettings;
|
||||
return (await response.json()) as MetadataSettings;
|
||||
};
|
||||
|
||||
const { data, error } = useSWR<metadataSettings>(
|
||||
'/api/v1/settings/metadatas'
|
||||
);
|
||||
// Obtenir la classe CSS pour l'affichage du statut
|
||||
const getStatusClass = (status: ProviderStatus): string => {
|
||||
switch (status) {
|
||||
case 'ok':
|
||||
return 'text-green-500';
|
||||
case 'not tested':
|
||||
return 'text-yellow-500';
|
||||
case 'failed':
|
||||
return 'text-red-500';
|
||||
}
|
||||
};
|
||||
|
||||
// Obtenir le message à afficher pour le statut
|
||||
const getStatusMessage = (status: ProviderStatus): string => {
|
||||
switch (status) {
|
||||
case 'ok':
|
||||
return intl.formatMessage(messages.ok);
|
||||
case 'not tested':
|
||||
return intl.formatMessage(messages.notTested);
|
||||
case 'failed':
|
||||
return intl.formatMessage(messages.failed);
|
||||
}
|
||||
};
|
||||
|
||||
const getBadgeType = (
|
||||
status: ProviderStatus
|
||||
):
|
||||
| 'default'
|
||||
| 'primary'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success'
|
||||
| 'dark'
|
||||
| 'light'
|
||||
| undefined => {
|
||||
switch (status) {
|
||||
case 'ok':
|
||||
return 'success';
|
||||
case 'not tested':
|
||||
return 'warning';
|
||||
case 'failed':
|
||||
return 'danger';
|
||||
}
|
||||
};
|
||||
|
||||
// Afficher un spinner pendant le chargement
|
||||
if (!data && !error) {
|
||||
return <LoadingSpinner />;
|
||||
}
|
||||
|
||||
const initialValues: MetadataValues = data?.metadata || {
|
||||
tv: IndexerType.TMDB,
|
||||
anime: IndexerType.TMDB,
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle
|
||||
@@ -106,107 +190,104 @@ const SettingsMetadata = () => {
|
||||
intl.formatMessage(globalMessages.settings),
|
||||
]}
|
||||
/>
|
||||
|
||||
<div className="mb-6">
|
||||
<h3 className="heading">{'Metadata'}</h3>
|
||||
<p className="description">{'Settings for metadata indexer'}</p>
|
||||
<h3 className="heading">Metadata</h3>
|
||||
<p className="description">
|
||||
{intl.formatMessage(messages.metadataSettings)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-6 rounded-lg bg-gray-800 p-4">
|
||||
<h4 className="mb-3 text-lg font-medium">
|
||||
{intl.formatMessage(messages.providerStatus)}
|
||||
</h4>
|
||||
<div className="flex flex-col space-y-3">
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 w-12">TMDB:</span>
|
||||
<span className={`text-sm ${getStatusClass(providerStatus.tmdb)}`}>
|
||||
<Badge badgeType={getBadgeType(providerStatus.tmdb)}>
|
||||
{getStatusMessage(providerStatus.tmdb)}
|
||||
</Badge>
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<span className="mr-2 w-12">TVDB:</span>
|
||||
<span className={`text-sm ${getStatusClass(providerStatus.tvdb)}`}>
|
||||
<Badge badgeType={getBadgeType(providerStatus.tvdb)}>
|
||||
{getStatusMessage(providerStatus.tvdb)}
|
||||
</Badge>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="section">
|
||||
<Formik
|
||||
initialValues={{
|
||||
settings: data?.settings ?? {
|
||||
tv: indexerType.TMDB,
|
||||
anime: indexerType.TMDB,
|
||||
},
|
||||
providers: data?.providers ?? {
|
||||
tvdb: {
|
||||
apiKey: '',
|
||||
pin: '',
|
||||
},
|
||||
},
|
||||
}}
|
||||
initialValues={{ metadata: initialValues }}
|
||||
onSubmit={async (values) => {
|
||||
try {
|
||||
await saveSettings(
|
||||
data ?? {
|
||||
providers: {
|
||||
tvdb: {
|
||||
apiKey: '',
|
||||
pin: '',
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
tv: indexerType.TMDB,
|
||||
anime: indexerType.TMDB,
|
||||
},
|
||||
}
|
||||
);
|
||||
await saveSettings(values.metadata);
|
||||
|
||||
if (data) {
|
||||
data.providers = values.providers;
|
||||
data.settings = values.settings;
|
||||
data.metadata = values.metadata;
|
||||
}
|
||||
|
||||
addToast('Metadata settings saved', { appearance: 'success' });
|
||||
} catch (e) {
|
||||
addToast('Failed to save Tvdb settings', { appearance: 'error' });
|
||||
return;
|
||||
addToast('Failed to save metadata settings', {
|
||||
appearance: 'error',
|
||||
});
|
||||
}
|
||||
addToast('Tvdb settings saved', { appearance: 'success' });
|
||||
}}
|
||||
>
|
||||
{({ isSubmitting, isValid, values }) => {
|
||||
{({ isSubmitting, isValid, values, setFieldValue }) => {
|
||||
return (
|
||||
<Form className="section" data-testid="settings-main-form">
|
||||
<div className="mb-6">
|
||||
<h2 className="heading">{'TVDB'}</h2>
|
||||
<p className="description">{'Settings for TVDB indexer'}</p>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="trustProxy" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.apiKey)}
|
||||
</span>
|
||||
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.enableTip)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
data-testid="tvdb-apiKey"
|
||||
type="text"
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
values.providers.tvdb.apiKey = e.target.value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="error"></div>
|
||||
<h2 className="heading">
|
||||
{intl.formatMessage(messages.indexerSelection)}
|
||||
</h2>
|
||||
<p className="description">
|
||||
{intl.formatMessage(messages.chooseProvider)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label htmlFor="trustProxy" className="checkbox-label">
|
||||
<label htmlFor="tvIndexer" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.pin)}
|
||||
</span>
|
||||
<span className="label-tip">
|
||||
{intl.formatMessage(messages.enableTip)}
|
||||
{intl.formatMessage(messages.seriesIndexer)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<Field
|
||||
data-testid="tvdb-pin"
|
||||
type="text"
|
||||
id="pin"
|
||||
name="pin"
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
values.providers.tvdb.pin = e.target.value;
|
||||
}}
|
||||
<MetadataSelector
|
||||
value={values.metadata.tv}
|
||||
onChange={(value) => setFieldValue('metadata.tv', value)}
|
||||
isDisabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label htmlFor="animeIndexer" className="checkbox-label">
|
||||
<span className="mr-2">
|
||||
{intl.formatMessage(messages.animeIndexer)}
|
||||
</span>
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<MetadataSelector
|
||||
value={values.metadata.anime}
|
||||
onChange={(value) =>
|
||||
setFieldValue('metadata.anime', value)
|
||||
}
|
||||
isDisabled={isSubmitting}
|
||||
/>
|
||||
</div>
|
||||
<div className="error"></div>
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
<div className="flex justify-end">
|
||||
{/* Bouton de test */}
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
buttonType="warning"
|
||||
@@ -215,17 +296,25 @@ const SettingsMetadata = () => {
|
||||
onClick={async () => {
|
||||
setIsTesting(true);
|
||||
try {
|
||||
await testConnection();
|
||||
addToast('Tvdb connection successful', {
|
||||
appearance: 'success',
|
||||
});
|
||||
const resp = await testConnection(values.metadata);
|
||||
|
||||
if (
|
||||
resp.tvdb === 'failed' ||
|
||||
resp.tmdb === 'failed'
|
||||
) {
|
||||
addToast('Test failed', { appearance: 'error' });
|
||||
} else {
|
||||
addToast('Connection test successful', {
|
||||
appearance: 'success',
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
addToast(
|
||||
'Tvdb connection error, check your credentials',
|
||||
{ appearance: 'error' }
|
||||
);
|
||||
addToast('Connection test failed', {
|
||||
appearance: 'error',
|
||||
});
|
||||
} finally {
|
||||
setIsTesting(false);
|
||||
}
|
||||
setIsTesting(false);
|
||||
}}
|
||||
>
|
||||
<BeakerIcon />
|
||||
@@ -236,12 +325,13 @@ const SettingsMetadata = () => {
|
||||
</span>
|
||||
</Button>
|
||||
</span>
|
||||
|
||||
<span className="ml-3 inline-flex rounded-md shadow-sm">
|
||||
<Button
|
||||
data-testid="tvbd-save-button"
|
||||
data-testid="metadata-save-button"
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
disabled={isSubmitting || !isValid}
|
||||
disabled={isSubmitting || !isValid || isTesting}
|
||||
>
|
||||
<ArrowDownOnSquareIcon />
|
||||
<span>
|
||||
|
||||
Reference in New Issue
Block a user