feat(requests): add language profile support (#860)
This commit is contained in:
@@ -376,9 +376,16 @@ components:
|
||||
activeDirectory:
|
||||
type: string
|
||||
example: '/tv/'
|
||||
activeLanguageProfileId:
|
||||
type: number
|
||||
example: 1
|
||||
nullable: true
|
||||
activeAnimeProfileId:
|
||||
type: number
|
||||
nullable: true
|
||||
activeAnimeLanguageProfileId:
|
||||
type: number
|
||||
nullable: true
|
||||
activeAnimeProfileName:
|
||||
type: string
|
||||
example: 720p/1080p
|
||||
@@ -3062,6 +3069,8 @@ paths:
|
||||
type: number
|
||||
rootFolder:
|
||||
type: string
|
||||
languageProfileId:
|
||||
type: number
|
||||
required:
|
||||
- mediaType
|
||||
- mediaId
|
||||
|
||||
@@ -112,6 +112,7 @@ interface AddSeriesOptions {
|
||||
tvdbid: number;
|
||||
title: string;
|
||||
profileId: number;
|
||||
languageProfileId?: number;
|
||||
seasons: number[];
|
||||
seasonFolder: boolean;
|
||||
rootFolderPath: string;
|
||||
@@ -120,6 +121,11 @@ interface AddSeriesOptions {
|
||||
searchNow?: boolean;
|
||||
}
|
||||
|
||||
export interface LanguageProfile {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
class SonarrAPI extends ExternalAPI {
|
||||
static buildSonarrUrl(sonarrSettings: SonarrSettings, path?: string): string {
|
||||
return `${sonarrSettings.useSsl ? 'https' : 'http'}://${
|
||||
@@ -236,6 +242,7 @@ class SonarrAPI extends ExternalAPI {
|
||||
tvdbId: options.tvdbid,
|
||||
title: options.title,
|
||||
profileId: options.profileId,
|
||||
languageProfileId: options.languageProfileId,
|
||||
seasons: this.buildSeasonList(
|
||||
options.seasons,
|
||||
series.seasons.map((season) => ({
|
||||
@@ -321,6 +328,28 @@ class SonarrAPI extends ExternalAPI {
|
||||
}
|
||||
}
|
||||
|
||||
public async getLanguageProfiles(): Promise<LanguageProfile[]> {
|
||||
try {
|
||||
const data = await this.getRolling<LanguageProfile[]>(
|
||||
'/v3/languageprofile',
|
||||
undefined,
|
||||
3600
|
||||
);
|
||||
|
||||
return data;
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
'Something went wrong while retrieving Sonarr language profiles.',
|
||||
{
|
||||
label: 'Sonarr API',
|
||||
message: e.message,
|
||||
}
|
||||
);
|
||||
|
||||
throw new Error('Failed to get language profiles');
|
||||
}
|
||||
}
|
||||
|
||||
private buildSeasonList(
|
||||
seasons: number[],
|
||||
existingSeasons?: SonarrSeason[]
|
||||
|
||||
@@ -78,6 +78,9 @@ export class MediaRequest {
|
||||
@Column({ nullable: true })
|
||||
public rootFolder: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
public languageProfileId: number;
|
||||
|
||||
constructor(init?: Partial<MediaRequest>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
@@ -559,6 +562,11 @@ export class MediaRequest {
|
||||
? sonarrSettings.activeAnimeProfileId
|
||||
: sonarrSettings.activeProfileId;
|
||||
|
||||
let languageProfile =
|
||||
seriesType === 'anime' && sonarrSettings.activeAnimeLanguageProfileId
|
||||
? sonarrSettings.activeAnimeLanguageProfileId
|
||||
: sonarrSettings.activeLanguageProfileId;
|
||||
|
||||
if (
|
||||
this.rootFolder &&
|
||||
this.rootFolder !== '' &&
|
||||
@@ -577,10 +585,24 @@ export class MediaRequest {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
this.languageProfileId &&
|
||||
this.languageProfileId !== languageProfile
|
||||
) {
|
||||
languageProfile = this.languageProfileId;
|
||||
logger.info(
|
||||
`Request has an override Language Profile: ${languageProfile}`,
|
||||
{
|
||||
label: 'Media Request',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Run this asynchronously so we don't wait for it on the UI side
|
||||
sonarr
|
||||
.addSeries({
|
||||
profileId: qualityProfile,
|
||||
languageProfileId: languageProfile,
|
||||
rootFolderPath: rootFolder,
|
||||
title: series.name,
|
||||
tvdbid: tvdbId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { RadarrProfile, RadarrRootFolder } from '../../api/radarr';
|
||||
import { LanguageProfile } from '../../api/sonarr';
|
||||
|
||||
export interface ServiceCommonServer {
|
||||
id: number;
|
||||
@@ -7,12 +8,15 @@ export interface ServiceCommonServer {
|
||||
isDefault: boolean;
|
||||
activeProfileId: number;
|
||||
activeDirectory: string;
|
||||
activeLanguageProfileId?: number;
|
||||
activeAnimeProfileId?: number;
|
||||
activeAnimeDirectory?: string;
|
||||
activeAnimeLanguageProfileId?: number;
|
||||
}
|
||||
|
||||
export interface ServiceCommonServerWithDetails {
|
||||
server: ServiceCommonServer;
|
||||
profiles: RadarrProfile[];
|
||||
rootFolders: Partial<RadarrRootFolder>[];
|
||||
languageProfiles?: LanguageProfile[];
|
||||
}
|
||||
|
||||
@@ -45,6 +45,8 @@ export interface SonarrSettings extends DVRSettings {
|
||||
activeAnimeProfileId?: number;
|
||||
activeAnimeProfileName?: string;
|
||||
activeAnimeDirectory?: string;
|
||||
activeAnimeLanguageProfileId?: number;
|
||||
activeLanguageProfileId?: number;
|
||||
enableSeasonFolders: boolean;
|
||||
}
|
||||
|
||||
|
||||
31
server/migration/1612571545781-AddLanguageProfileId.ts
Normal file
31
server/migration/1612571545781-AddLanguageProfileId.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddLanguageProfileId1612571545781 implements MigrationInterface {
|
||||
name = 'AddLanguageProfileId1612571545781';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "temporary_media_request" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "status" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "type" varchar NOT NULL, "mediaId" integer, "requestedById" integer, "modifiedById" integer, "is4k" boolean NOT NULL DEFAULT (0), "serverId" integer, "profileId" integer, "rootFolder" varchar, "languageProfileId" integer, CONSTRAINT "FK_a1aa713f41c99e9d10c48da75a0" FOREIGN KEY ("mediaId") REFERENCES "media" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_6997bee94720f1ecb7f31137095" FOREIGN KEY ("requestedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_f4fc4efa14c3ba2b29c4525fa15" FOREIGN KEY ("modifiedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "temporary_media_request"("id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder") SELECT "id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder" FROM "media_request"`
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "media_request"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "temporary_media_request" RENAME TO "media_request"`
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "media_request" RENAME TO "temporary_media_request"`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "media_request" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "status" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "type" varchar NOT NULL, "mediaId" integer, "requestedById" integer, "modifiedById" integer, "is4k" boolean NOT NULL DEFAULT (0), "serverId" integer, "profileId" integer, "rootFolder" varchar, CONSTRAINT "FK_a1aa713f41c99e9d10c48da75a0" FOREIGN KEY ("mediaId") REFERENCES "media" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_6997bee94720f1ecb7f31137095" FOREIGN KEY ("requestedById") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_f4fc4efa14c3ba2b29c4525fa15" FOREIGN KEY ("modifiedById") REFERENCES "user" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`
|
||||
);
|
||||
await queryRunner.query(
|
||||
`INSERT INTO "media_request"("id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder") SELECT "id", "status", "createdAt", "updatedAt", "type", "mediaId", "requestedById", "modifiedById", "is4k", "serverId", "profileId", "rootFolder" FROM "temporary_media_request"`
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "temporary_media_request"`);
|
||||
}
|
||||
}
|
||||
@@ -250,6 +250,7 @@ requestRoutes.post(
|
||||
serverId: req.body.serverId,
|
||||
profileId: req.body.profileId,
|
||||
rootFolder: req.body.rootFolder,
|
||||
languageProfileId: req.body.languageProfileId,
|
||||
seasons: finalSeasons.map(
|
||||
(sn) =>
|
||||
new SeasonRequest({
|
||||
|
||||
@@ -90,6 +90,8 @@ serviceRoutes.get('/sonarr', async (req, res) => {
|
||||
activeProfileId: sonarr.activeProfileId,
|
||||
activeAnimeProfileId: sonarr.activeAnimeProfileId,
|
||||
activeAnimeDirectory: sonarr.activeAnimeDirectory,
|
||||
activeLanguageProfileId: sonarr.activeLanguageProfileId,
|
||||
activeAnimeLanguageProfileId: sonarr.activeAnimeLanguageProfileId,
|
||||
})
|
||||
);
|
||||
|
||||
@@ -119,8 +121,10 @@ serviceRoutes.get<{ sonarrId: string }>(
|
||||
}:${sonarrSettings.port}${sonarrSettings.baseUrl ?? ''}/api`,
|
||||
});
|
||||
|
||||
try {
|
||||
const profiles = await sonarr.getProfiles();
|
||||
const rootFolders = await sonarr.getRootFolders();
|
||||
const languageProfiles = await sonarr.getLanguageProfiles();
|
||||
|
||||
return res.status(200).json({
|
||||
server: {
|
||||
@@ -132,6 +136,9 @@ serviceRoutes.get<{ sonarrId: string }>(
|
||||
activeProfileId: sonarrSettings.activeProfileId,
|
||||
activeAnimeProfileId: sonarrSettings.activeAnimeProfileId,
|
||||
activeAnimeDirectory: sonarrSettings.activeAnimeDirectory,
|
||||
activeLanguageProfileId: sonarrSettings.activeLanguageProfileId,
|
||||
activeAnimeLanguageProfileId:
|
||||
sonarrSettings.activeAnimeLanguageProfileId,
|
||||
},
|
||||
profiles: profiles.map((profile) => ({
|
||||
id: profile.id,
|
||||
@@ -143,7 +150,11 @@ serviceRoutes.get<{ sonarrId: string }>(
|
||||
path: folder.path,
|
||||
totalSpace: folder.totalSpace,
|
||||
})),
|
||||
languageProfiles: languageProfiles,
|
||||
} as ServiceCommonServerWithDetails);
|
||||
} catch (e) {
|
||||
next({ status: 500, message: e.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -46,6 +46,7 @@ sonarrRoutes.post('/test', async (req, res, next) => {
|
||||
|
||||
const profiles = await sonarr.getProfiles();
|
||||
const folders = await sonarr.getRootFolders();
|
||||
const languageProfiles = await sonarr.getLanguageProfiles();
|
||||
|
||||
return res.status(200).json({
|
||||
profiles,
|
||||
@@ -53,6 +54,7 @@ sonarrRoutes.post('/test', async (req, res, next) => {
|
||||
id: folder.id,
|
||||
path: folder.path,
|
||||
})),
|
||||
languageProfiles,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Failed to test Sonarr', {
|
||||
|
||||
@@ -21,12 +21,15 @@ const messages = defineMessages({
|
||||
loadingprofiles: 'Loading profiles…',
|
||||
loadingfolders: 'Loading folders…',
|
||||
requestas: 'Request As',
|
||||
languageprofile: 'Language Profile',
|
||||
loadinglanguages: 'Loading languages…',
|
||||
});
|
||||
|
||||
export type RequestOverrides = {
|
||||
server?: number;
|
||||
profile?: number;
|
||||
folder?: string;
|
||||
language?: number;
|
||||
user?: User;
|
||||
};
|
||||
|
||||
@@ -69,6 +72,11 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
const [selectedFolder, setSelectedFolder] = useState<string>(
|
||||
defaultOverrides?.folder ?? ''
|
||||
);
|
||||
|
||||
const [selectedLanguage, setSelectedLanguage] = useState<number>(
|
||||
defaultOverrides?.language ?? -1
|
||||
);
|
||||
|
||||
const {
|
||||
data: serverData,
|
||||
isValidating,
|
||||
@@ -135,6 +143,13 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
? serverData.server.activeAnimeDirectory
|
||||
: serverData.server.activeDirectory)
|
||||
);
|
||||
const defaultLanguage = serverData.languageProfiles?.find(
|
||||
(language) =>
|
||||
language.id ===
|
||||
(isAnime
|
||||
? serverData.server.activeAnimeLanguageProfileId
|
||||
: serverData.server.activeLanguageProfileId)
|
||||
);
|
||||
|
||||
if (
|
||||
defaultProfile &&
|
||||
@@ -149,7 +164,15 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
defaultFolder.path !== selectedFolder &&
|
||||
(!defaultOverrides || defaultOverrides.folder === null)
|
||||
) {
|
||||
setSelectedFolder(defaultFolder?.path ?? '');
|
||||
setSelectedFolder(defaultFolder.path ?? '');
|
||||
}
|
||||
|
||||
if (
|
||||
defaultLanguage &&
|
||||
defaultLanguage.id !== selectedLanguage &&
|
||||
(!defaultOverrides || defaultOverrides.language === null)
|
||||
) {
|
||||
setSelectedLanguage(defaultLanguage.id);
|
||||
}
|
||||
}
|
||||
}, [serverData]);
|
||||
@@ -178,10 +201,19 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
) {
|
||||
setSelectedFolder(defaultOverrides.folder);
|
||||
}
|
||||
|
||||
if (
|
||||
defaultOverrides &&
|
||||
defaultOverrides.language !== null &&
|
||||
defaultOverrides.language !== undefined
|
||||
) {
|
||||
setSelectedLanguage(defaultOverrides.language);
|
||||
}
|
||||
}, [
|
||||
defaultOverrides?.server,
|
||||
defaultOverrides?.folder,
|
||||
defaultOverrides?.profile,
|
||||
defaultOverrides?.language,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -191,9 +223,16 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
profile: selectedProfile !== -1 ? selectedProfile : undefined,
|
||||
server: selectedServer ?? undefined,
|
||||
user: selectedUser ?? undefined,
|
||||
language: selectedLanguage ?? undefined,
|
||||
});
|
||||
}
|
||||
}, [selectedFolder, selectedServer, selectedProfile, selectedUser]);
|
||||
}, [
|
||||
selectedFolder,
|
||||
selectedServer,
|
||||
selectedProfile,
|
||||
selectedUser,
|
||||
selectedLanguage,
|
||||
]);
|
||||
|
||||
if (!data && !error) {
|
||||
return (
|
||||
@@ -225,7 +264,7 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
{!!data && selectedServer !== null && (
|
||||
<>
|
||||
<div className="flex flex-col items-center justify-between md:flex-row">
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:pr-4 md:mb-0">
|
||||
<label htmlFor="server" className="text-label">
|
||||
{intl.formatMessage(messages.destinationserver)}
|
||||
</label>
|
||||
@@ -247,8 +286,8 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:pr-4 md:mb-0">
|
||||
<label htmlFor="server" className="text-label">
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:pr-4 md:mb-0">
|
||||
<label htmlFor="profile" className="text-label">
|
||||
{intl.formatMessage(messages.qualityprofile)}
|
||||
</label>
|
||||
<select
|
||||
@@ -283,8 +322,12 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/3 md:mb-0">
|
||||
<label htmlFor="server" className="text-label">
|
||||
<div
|
||||
className={`flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:mb-0 ${
|
||||
type === 'tv' ? 'md:pr-4' : ''
|
||||
}`}
|
||||
>
|
||||
<label htmlFor="folder" className="text-label">
|
||||
{intl.formatMessage(messages.rootfolder)}
|
||||
</label>
|
||||
<select
|
||||
@@ -319,6 +362,50 @@ const AdvancedRequester: React.FC<AdvancedRequesterProps> = ({
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{type === 'tv' && (
|
||||
<div className="flex-grow flex-shrink-0 w-full mb-2 md:w-1/4 md:mb-0">
|
||||
<label htmlFor="language" className="text-label">
|
||||
{intl.formatMessage(messages.languageprofile)}
|
||||
</label>
|
||||
<select
|
||||
id="language"
|
||||
name="language"
|
||||
value={selectedLanguage}
|
||||
onChange={(e) =>
|
||||
setSelectedLanguage(parseInt(e.target.value))
|
||||
}
|
||||
onBlur={(e) =>
|
||||
setSelectedLanguage(parseInt(e.target.value))
|
||||
}
|
||||
className="block w-full py-2 pl-3 pr-10 mt-1 text-base leading-6 text-white transition duration-150 ease-in-out bg-gray-800 border-gray-700 rounded-md form-select focus:outline-none focus:ring-blue focus:border-blue-300 sm:text-sm sm:leading-5"
|
||||
>
|
||||
{isValidating && (
|
||||
<option value="">
|
||||
{intl.formatMessage(messages.loadinglanguages)}
|
||||
</option>
|
||||
)}
|
||||
{!isValidating &&
|
||||
serverData &&
|
||||
serverData.languageProfiles?.map((language) => (
|
||||
<option
|
||||
key={`folder-list${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
{isAnime &&
|
||||
serverData.server.activeAnimeLanguageProfileId ===
|
||||
language.id
|
||||
? ` ${intl.formatMessage(messages.default)}`
|
||||
: !isAnime &&
|
||||
serverData.server.activeLanguageProfileId ===
|
||||
language.id
|
||||
? ` ${intl.formatMessage(messages.default)}`
|
||||
: ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -103,6 +103,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
serverId: requestOverrides?.server,
|
||||
profileId: requestOverrides?.profile,
|
||||
rootFolder: requestOverrides?.folder,
|
||||
languageProfileId: requestOverrides?.language,
|
||||
userId: requestOverrides?.user?.id,
|
||||
seasons: selectedSeasons,
|
||||
});
|
||||
@@ -151,6 +152,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
serverId: requestOverrides.server,
|
||||
profileId: requestOverrides.profile,
|
||||
rootFolder: requestOverrides.folder,
|
||||
languageProfileId: requestOverrides.language,
|
||||
userId: requestOverrides?.user?.id,
|
||||
};
|
||||
}
|
||||
@@ -569,6 +571,7 @@ const TvRequestModal: React.FC<RequestModalProps> = ({
|
||||
folder: editRequest.rootFolder,
|
||||
profile: editRequest.profileId,
|
||||
server: editRequest.serverId,
|
||||
language: editRequest.languageProfileId,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ const messages = defineMessages({
|
||||
validationPortRequired: 'You must provide a port',
|
||||
validationApiKeyRequired: 'You must provide an API key',
|
||||
validationRootFolderRequired: 'You must select a root folder',
|
||||
validationProfileRequired: 'You must select a profile',
|
||||
validationProfileRequired: 'You must select a quality profile',
|
||||
validationLanguageProfileRequired: 'You must select a language profile',
|
||||
toastSonarrTestSuccess: 'Sonarr connection established!',
|
||||
toastSonarrTestFailure: 'Failed to connect to Sonarr.',
|
||||
saving: 'Saving…',
|
||||
@@ -35,17 +36,22 @@ const messages = defineMessages({
|
||||
baseUrl: 'Base URL',
|
||||
baseUrlPlaceholder: 'Example: /sonarr',
|
||||
qualityprofile: 'Quality Profile',
|
||||
languageprofile: 'Language Profile',
|
||||
rootfolder: 'Root Folder',
|
||||
animequalityprofile: 'Anime Quality Profile',
|
||||
animelanguageprofile: 'Anime Language Profile',
|
||||
animerootfolder: 'Anime Root Folder',
|
||||
seasonfolders: 'Season Folders',
|
||||
server4k: '4K Server',
|
||||
selectQualityProfile: 'Select quality profile',
|
||||
selectRootFolder: 'Select root folder',
|
||||
selectLanguageProfile: 'Select language profile',
|
||||
loadingprofiles: 'Loading quality profiles…',
|
||||
testFirstQualityProfiles: 'Test connection to load quality profiles',
|
||||
loadingrootfolders: 'Loading root folders…',
|
||||
testFirstRootFolders: 'Test connection to load root folders',
|
||||
loadinglanguageprofiles: 'Loading language profiles…',
|
||||
testFirstLanguageProfiles: 'Test connection to load language profiles',
|
||||
syncEnabled: 'Enable Sync',
|
||||
externalUrl: 'External URL',
|
||||
externalUrlPlaceholder: 'External URL pointing to your Sonarr server',
|
||||
@@ -65,6 +71,10 @@ interface TestResponse {
|
||||
id: number;
|
||||
path: string;
|
||||
}[];
|
||||
languageProfiles: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
interface SonarrModalProps {
|
||||
@@ -86,6 +96,7 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
const [testResponse, setTestResponse] = useState<TestResponse>({
|
||||
profiles: [],
|
||||
rootFolders: [],
|
||||
languageProfiles: [],
|
||||
});
|
||||
const SonarrSettingsSchema = Yup.object().shape({
|
||||
name: Yup.string().required(
|
||||
@@ -106,6 +117,9 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
activeProfileId: Yup.string().required(
|
||||
intl.formatMessage(messages.validationProfileRequired)
|
||||
),
|
||||
activeLanguageProfileId: Yup.number().required(
|
||||
intl.formatMessage(messages.validationLanguageProfileRequired)
|
||||
),
|
||||
externalUrl: Yup.string()
|
||||
.url(intl.formatMessage(messages.validationApplicationUrl))
|
||||
.test(
|
||||
@@ -224,8 +238,10 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
apiKey: sonarr?.apiKey,
|
||||
baseUrl: sonarr?.baseUrl,
|
||||
activeProfileId: sonarr?.activeProfileId,
|
||||
activeLanguageProfileId: sonarr?.activeLanguageProfileId,
|
||||
rootFolder: sonarr?.activeDirectory,
|
||||
activeAnimeProfileId: sonarr?.activeAnimeProfileId,
|
||||
activeAnimeLanguageProfileId: sonarr?.activeAnimeLanguageProfileId,
|
||||
activeAnimeRootFolder: sonarr?.activeAnimeDirectory,
|
||||
isDefault: sonarr?.isDefault ?? false,
|
||||
is4k: sonarr?.is4k ?? false,
|
||||
@@ -252,11 +268,17 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
useSsl: values.ssl,
|
||||
baseUrl: values.baseUrl,
|
||||
activeProfileId: Number(values.activeProfileId),
|
||||
activeLanguageProfileId: values.activeLanguageProfileId
|
||||
? Number(values.activeLanguageProfileId)
|
||||
: undefined,
|
||||
activeProfileName: profileName,
|
||||
activeDirectory: values.rootFolder,
|
||||
activeAnimeProfileId: values.activeAnimeProfileId
|
||||
? Number(values.activeAnimeProfileId)
|
||||
: undefined,
|
||||
activeAnimeLanguageProfileId: values.activeAnimeLanguageProfileId
|
||||
? Number(values.activeAnimeLanguageProfileId)
|
||||
: undefined,
|
||||
activeAnimeProfileName: animeProfileName ?? undefined,
|
||||
activeAnimeDirectory: values.activeAnimeRootFolder,
|
||||
is4k: values.is4k,
|
||||
@@ -559,6 +581,54 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.languageprofile)}
|
||||
<span className="text-red-500">*</span>
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeLanguageProfileId"
|
||||
name="activeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{errors.activeLanguageProfileId &&
|
||||
touched.activeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label htmlFor="activeAnimeProfileId" className="text-label">
|
||||
{intl.formatMessage(messages.animequalityprofile)}
|
||||
@@ -635,6 +705,53 @@ const SonarrModal: React.FC<SonarrModalProps> = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="activeAnimeLanguageProfileId"
|
||||
className="text-label"
|
||||
>
|
||||
{intl.formatMessage(messages.animerootfolder)}
|
||||
</label>
|
||||
<div className="form-input">
|
||||
<div className="flex max-w-lg rounded-md shadow-sm">
|
||||
<Field
|
||||
as="select"
|
||||
id="activeAnimeLanguageProfileId"
|
||||
name="activeAnimeLanguageProfileId"
|
||||
disabled={!isValidated || isTesting}
|
||||
>
|
||||
<option value="">
|
||||
{isTesting
|
||||
? intl.formatMessage(
|
||||
messages.loadinglanguageprofiles
|
||||
)
|
||||
: !isValidated
|
||||
? intl.formatMessage(
|
||||
messages.testFirstLanguageProfiles
|
||||
)
|
||||
: intl.formatMessage(
|
||||
messages.selectLanguageProfile
|
||||
)}
|
||||
</option>
|
||||
{testResponse.languageProfiles.length > 0 &&
|
||||
testResponse.languageProfiles.map((language) => (
|
||||
<option
|
||||
key={`loaded-profile-${language.id}`}
|
||||
value={language.id}
|
||||
>
|
||||
{language.name}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</div>
|
||||
{errors.activeAnimeLanguageProfileId &&
|
||||
touched.activeAnimeLanguageProfileId && (
|
||||
<div className="error">
|
||||
{errors.activeAnimeLanguageProfileId}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-row">
|
||||
<label
|
||||
htmlFor="enableSeasonFolders"
|
||||
|
||||
@@ -169,7 +169,9 @@
|
||||
"components.RequestModal.AdvancedRequester.animenote": "* This series is an anime.",
|
||||
"components.RequestModal.AdvancedRequester.default": "(Default)",
|
||||
"components.RequestModal.AdvancedRequester.destinationserver": "Destination Server",
|
||||
"components.RequestModal.AdvancedRequester.languageprofile": "Language Profile",
|
||||
"components.RequestModal.AdvancedRequester.loadingfolders": "Loading folders…",
|
||||
"components.RequestModal.AdvancedRequester.loadinglanguages": "Loading languages…",
|
||||
"components.RequestModal.AdvancedRequester.loadingprofiles": "Loading profiles…",
|
||||
"components.RequestModal.AdvancedRequester.qualityprofile": "Quality Profile",
|
||||
"components.RequestModal.AdvancedRequester.requestas": "Request As",
|
||||
@@ -391,6 +393,7 @@
|
||||
"components.Settings.SettingsJobsCache.process": "Process",
|
||||
"components.Settings.SettingsJobsCache.runnow": "Run Now",
|
||||
"components.Settings.SonarrModal.add": "Add Server",
|
||||
"components.Settings.SonarrModal.animelanguageprofile": "Anime Language Profile",
|
||||
"components.Settings.SonarrModal.animequalityprofile": "Anime Quality Profile",
|
||||
"components.Settings.SonarrModal.animerootfolder": "Anime Root Folder",
|
||||
"components.Settings.SonarrModal.apiKey": "API Key",
|
||||
@@ -403,6 +406,8 @@
|
||||
"components.Settings.SonarrModal.externalUrl": "External URL",
|
||||
"components.Settings.SonarrModal.externalUrlPlaceholder": "External URL pointing to your Sonarr server",
|
||||
"components.Settings.SonarrModal.hostname": "Hostname",
|
||||
"components.Settings.SonarrModal.languageprofile": "Language Profile",
|
||||
"components.Settings.SonarrModal.loadinglanguageprofiles": "Loading language profiles…",
|
||||
"components.Settings.SonarrModal.loadingprofiles": "Loading quality profiles…",
|
||||
"components.Settings.SonarrModal.loadingrootfolders": "Loading root folders…",
|
||||
"components.Settings.SonarrModal.port": "Port",
|
||||
@@ -412,6 +417,7 @@
|
||||
"components.Settings.SonarrModal.save": "Save Changes",
|
||||
"components.Settings.SonarrModal.saving": "Saving…",
|
||||
"components.Settings.SonarrModal.seasonfolders": "Season Folders",
|
||||
"components.Settings.SonarrModal.selectLanguageProfile": "Select language profile",
|
||||
"components.Settings.SonarrModal.selectQualityProfile": "Select quality profile",
|
||||
"components.Settings.SonarrModal.selectRootFolder": "Select root folder",
|
||||
"components.Settings.SonarrModal.server4k": "4K Server",
|
||||
@@ -420,6 +426,7 @@
|
||||
"components.Settings.SonarrModal.ssl": "SSL",
|
||||
"components.Settings.SonarrModal.syncEnabled": "Enable Sync",
|
||||
"components.Settings.SonarrModal.test": "Test",
|
||||
"components.Settings.SonarrModal.testFirstLanguageProfiles": "Test connection to load language profiles",
|
||||
"components.Settings.SonarrModal.testFirstQualityProfiles": "Test connection to load quality profiles",
|
||||
"components.Settings.SonarrModal.testFirstRootFolders": "Test connection to load root folders",
|
||||
"components.Settings.SonarrModal.testing": "Testing…",
|
||||
@@ -431,6 +438,7 @@
|
||||
"components.Settings.SonarrModal.validationBaseUrlLeadingSlash": "Base URL must have a leading slash",
|
||||
"components.Settings.SonarrModal.validationBaseUrlTrailingSlash": "Base URL must not end in a trailing slash",
|
||||
"components.Settings.SonarrModal.validationHostnameRequired": "You must provide a hostname/IP",
|
||||
"components.Settings.SonarrModal.validationLanguageProfileRequired": "You must select a language profile",
|
||||
"components.Settings.SonarrModal.validationNameRequired": "You must provide a server name",
|
||||
"components.Settings.SonarrModal.validationPortRequired": "You must provide a port",
|
||||
"components.Settings.SonarrModal.validationProfileRequired": "You must select a quality profile",
|
||||
|
||||
Reference in New Issue
Block a user