Compare commits
3 Commits
preview-re
...
fallenbage
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b92a26bfcb | ||
|
|
0753d7f48e | ||
|
|
6f9988085b |
@@ -24,6 +24,10 @@ Set this to the username and password for your ntfy.sh server.
|
||||
|
||||
Set this to the token for your ntfy.sh server.
|
||||
|
||||
### Priority (optional)
|
||||
|
||||
Set the priority level for notifications. Options range from Minimum (1) to Urgent (5), with Default (3) being the standard level. Higher priority notifications may bypass Do Not Disturb settings on some devices.
|
||||
|
||||
:::info
|
||||
Please refer to the [ntfy.sh API documentation](https://docs.ntfy.sh/) for more details on configuring these notifications.
|
||||
:::
|
||||
|
||||
@@ -209,34 +209,6 @@ class SonarrAPI extends ServarrBase<{
|
||||
series: newSeriesResponse.data,
|
||||
});
|
||||
|
||||
try {
|
||||
const episodes = await this.getEpisodes(newSeriesResponse.data.id);
|
||||
const episodeIdsToMonitor = episodes
|
||||
.filter(
|
||||
(ep) =>
|
||||
options.seasons.includes(ep.seasonNumber) && !ep.monitored
|
||||
)
|
||||
.map((ep) => ep.id);
|
||||
|
||||
if (episodeIdsToMonitor.length > 0) {
|
||||
logger.debug(
|
||||
'Re-monitoring unmonitored episodes for requested seasons.',
|
||||
{
|
||||
label: 'Sonarr',
|
||||
seriesId: newSeriesResponse.data.id,
|
||||
episodeCount: episodeIdsToMonitor.length,
|
||||
}
|
||||
);
|
||||
await this.monitorEpisodes(episodeIdsToMonitor);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('Failed to re-monitor episodes', {
|
||||
label: 'Sonarr',
|
||||
errorMessage: e.message,
|
||||
seriesId: newSeriesResponse.data.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (options.searchNow) {
|
||||
this.searchSeries(newSeriesResponse.data.id);
|
||||
}
|
||||
@@ -346,38 +318,6 @@ class SonarrAPI extends ServarrBase<{
|
||||
}
|
||||
}
|
||||
|
||||
public async getEpisodes(seriesId: number): Promise<EpisodeResult[]> {
|
||||
try {
|
||||
const response = await this.axios.get<EpisodeResult[]>('/episode', {
|
||||
params: { seriesId },
|
||||
});
|
||||
return response.data;
|
||||
} catch (e) {
|
||||
logger.error('Failed to retrieve episodes', {
|
||||
label: 'Sonarr API',
|
||||
errorMessage: e.message,
|
||||
seriesId,
|
||||
});
|
||||
throw new Error('Failed to get episodes');
|
||||
}
|
||||
}
|
||||
|
||||
public async monitorEpisodes(episodeIds: number[]): Promise<void> {
|
||||
try {
|
||||
await this.axios.put('/episode/monitor', {
|
||||
episodeIds,
|
||||
monitored: true,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Failed to monitor episodes', {
|
||||
label: 'Sonarr API',
|
||||
errorMessage: e.message,
|
||||
episodeIds,
|
||||
});
|
||||
throw new Error('Failed to monitor episodes');
|
||||
}
|
||||
}
|
||||
|
||||
private buildSeasonList(
|
||||
seasons: number[],
|
||||
existingSeasons?: SonarrSeason[]
|
||||
|
||||
@@ -300,6 +300,7 @@ class AvailabilitySync {
|
||||
// Sonarr finds that season, we will change the final seasons value
|
||||
// to true.
|
||||
const filteredSeasonsMap: Map<number, boolean> = new Map();
|
||||
|
||||
media.seasons
|
||||
.filter(
|
||||
(season) =>
|
||||
@@ -310,7 +311,48 @@ class AvailabilitySync {
|
||||
filteredSeasonsMap.set(season.seasonNumber, false)
|
||||
);
|
||||
|
||||
// non-4k
|
||||
const finalSeasons: Map<number, boolean> = new Map();
|
||||
|
||||
if (mediaServerType === MediaServerType.PLEX) {
|
||||
plexSeasonsMap.forEach((value, key) => {
|
||||
finalSeasons.set(key, value);
|
||||
});
|
||||
|
||||
filteredSeasonsMap.forEach((value, key) => {
|
||||
if (!finalSeasons.has(key)) {
|
||||
finalSeasons.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
sonarrSeasonsMap.forEach((value, key) => {
|
||||
if (!finalSeasons.has(key)) {
|
||||
finalSeasons.set(key, value);
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
mediaServerType === MediaServerType.JELLYFIN ||
|
||||
mediaServerType === MediaServerType.EMBY
|
||||
) {
|
||||
jellyfinSeasonsMap.forEach((value, key) => {
|
||||
finalSeasons.set(key, value);
|
||||
});
|
||||
|
||||
filteredSeasonsMap.forEach((value, key) => {
|
||||
if (!finalSeasons.has(key)) {
|
||||
finalSeasons.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
sonarrSeasonsMap.forEach((value, key) => {
|
||||
if (!finalSeasons.has(key)) {
|
||||
finalSeasons.set(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const filteredSeasonsMap4k: Map<number, boolean> = new Map();
|
||||
|
||||
media.seasons
|
||||
.filter(
|
||||
(season) =>
|
||||
@@ -321,32 +363,44 @@ class AvailabilitySync {
|
||||
filteredSeasonsMap4k.set(season.seasonNumber, false)
|
||||
);
|
||||
|
||||
let finalSeasons: Map<number, boolean>;
|
||||
let finalSeasons4k: Map<number, boolean>;
|
||||
// 4k
|
||||
const finalSeasons4k: Map<number, boolean> = new Map();
|
||||
|
||||
if (mediaServerType === MediaServerType.PLEX) {
|
||||
finalSeasons = new Map([
|
||||
...filteredSeasonsMap,
|
||||
...plexSeasonsMap,
|
||||
...sonarrSeasonsMap,
|
||||
]);
|
||||
finalSeasons4k = new Map([
|
||||
...filteredSeasonsMap4k,
|
||||
...plexSeasonsMap4k,
|
||||
...sonarrSeasonsMap4k,
|
||||
]);
|
||||
} else {
|
||||
// Jellyfin/Emby
|
||||
finalSeasons = new Map([
|
||||
...filteredSeasonsMap,
|
||||
...jellyfinSeasonsMap,
|
||||
...sonarrSeasonsMap,
|
||||
]);
|
||||
finalSeasons4k = new Map([
|
||||
...filteredSeasonsMap4k,
|
||||
...jellyfinSeasonsMap4k,
|
||||
...sonarrSeasonsMap4k,
|
||||
]);
|
||||
plexSeasonsMap4k.forEach((value, key) => {
|
||||
finalSeasons4k.set(key, value);
|
||||
});
|
||||
|
||||
filteredSeasonsMap4k.forEach((value, key) => {
|
||||
if (!finalSeasons4k.has(key)) {
|
||||
finalSeasons4k.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
sonarrSeasonsMap4k.forEach((value, key) => {
|
||||
if (!finalSeasons4k.has(key)) {
|
||||
finalSeasons4k.set(key, value);
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
mediaServerType === MediaServerType.JELLYFIN ||
|
||||
mediaServerType === MediaServerType.EMBY
|
||||
) {
|
||||
jellyfinSeasonsMap4k.forEach((value, key) => {
|
||||
finalSeasons4k.set(key, value);
|
||||
});
|
||||
|
||||
filteredSeasonsMap4k.forEach((value, key) => {
|
||||
if (!finalSeasons4k.has(key)) {
|
||||
finalSeasons4k.set(key, value);
|
||||
}
|
||||
});
|
||||
|
||||
sonarrSeasonsMap4k.forEach((value, key) => {
|
||||
if (!finalSeasons4k.has(key)) {
|
||||
finalSeasons4k.set(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -939,8 +993,8 @@ class AvailabilitySync {
|
||||
existsInJellyfin = true;
|
||||
}
|
||||
} catch (ex) {
|
||||
if (!ex.message.includes('404') && !ex.message.includes('500')) {
|
||||
existsInJellyfin = true;
|
||||
if (!ex.message.includes('404' || '500')) {
|
||||
existsInJellyfin = false;
|
||||
preventSeasonSearch = true;
|
||||
logger.debug(
|
||||
`Failure retrieving the ${is4k ? '4K' : 'non-4K'} ${
|
||||
|
||||
@@ -27,7 +27,7 @@ class NtfyAgent
|
||||
const { embedPoster } = settings.notifications.agents.ntfy;
|
||||
|
||||
const topic = this.getSettings().options.topic;
|
||||
const priority = 3;
|
||||
const priority = this.getSettings().options.priority ?? 3;
|
||||
|
||||
const title = payload.event
|
||||
? `${payload.event} - ${payload.subject}`
|
||||
|
||||
@@ -45,17 +45,7 @@ class PushoverAgent
|
||||
}
|
||||
|
||||
public shouldSend(): boolean {
|
||||
const settings = this.getSettings();
|
||||
|
||||
if (
|
||||
settings.enabled &&
|
||||
settings.options.accessToken &&
|
||||
settings.options.userToken
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private async getImagePayload(
|
||||
|
||||
@@ -115,11 +115,9 @@ class BaseScanner<T> {
|
||||
let changedExisting = false;
|
||||
|
||||
if (existing[is4k ? 'status4k' : 'status'] !== MediaStatus.AVAILABLE) {
|
||||
existing[is4k ? 'status4k' : 'status'] = !processing
|
||||
? MediaStatus.AVAILABLE
|
||||
: existing[is4k ? 'status4k' : 'status'] === MediaStatus.DELETED
|
||||
? MediaStatus.DELETED
|
||||
: MediaStatus.PROCESSING;
|
||||
existing[is4k ? 'status4k' : 'status'] = processing
|
||||
? MediaStatus.PROCESSING
|
||||
: MediaStatus.AVAILABLE;
|
||||
if (mediaAddedAt) {
|
||||
existing.mediaAddedAt = mediaAddedAt;
|
||||
}
|
||||
@@ -332,11 +330,6 @@ class BaseScanner<T> {
|
||||
season.processing &&
|
||||
existingSeason.status !== MediaStatus.DELETED
|
||||
? MediaStatus.PROCESSING
|
||||
: !season.is4kOverride &&
|
||||
!season.processing &&
|
||||
season.episodes === 0 &&
|
||||
existingSeason.status === MediaStatus.PROCESSING
|
||||
? MediaStatus.UNKNOWN
|
||||
: existingSeason.status;
|
||||
|
||||
// Same thing here, except we only do updates if 4k is enabled
|
||||
@@ -352,11 +345,6 @@ class BaseScanner<T> {
|
||||
season.processing &&
|
||||
existingSeason.status4k !== MediaStatus.DELETED
|
||||
? MediaStatus.PROCESSING
|
||||
: season.is4kOverride &&
|
||||
!season.processing &&
|
||||
season.episodes4k === 0 &&
|
||||
existingSeason.status4k === MediaStatus.PROCESSING
|
||||
? MediaStatus.UNKNOWN
|
||||
: existingSeason.status4k;
|
||||
} else {
|
||||
newSeasons.push(
|
||||
|
||||
@@ -296,6 +296,7 @@ export interface NotificationAgentNtfy extends NotificationAgentConfig {
|
||||
password?: string;
|
||||
authMethodToken?: boolean;
|
||||
token?: string;
|
||||
priority?: number;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -529,6 +530,7 @@ class Settings {
|
||||
options: {
|
||||
url: '',
|
||||
topic: '',
|
||||
priority: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import Tooltip from '@app/components/Common/Tooltip';
|
||||
import RequestModal from '@app/components/RequestModal';
|
||||
import StatusBadge from '@app/components/StatusBadge';
|
||||
import useDeepLinks from '@app/hooks/useDeepLinks';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
@@ -218,6 +219,7 @@ interface RequestCardProps {
|
||||
}
|
||||
|
||||
const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
const settings = useSettings();
|
||||
const { ref, inView } = useInView({
|
||||
triggerOnce: true,
|
||||
});
|
||||
@@ -400,7 +402,14 @@ const RequestCard = ({ request, onTitleData }: RequestCardProps) => {
|
||||
<div className="my-0.5 hidden items-center text-sm sm:my-1 sm:flex">
|
||||
<span className="mr-2 font-bold ">
|
||||
{intl.formatMessage(messages.seasons, {
|
||||
seasonCount: request.seasons.length,
|
||||
seasonCount:
|
||||
(settings.currentSettings.enableSpecialEpisodes
|
||||
? title.seasons.length
|
||||
: title.seasons.filter(
|
||||
(season) => season.seasonNumber !== 0
|
||||
).length) === request.seasons.length
|
||||
? 0
|
||||
: request.seasons.length,
|
||||
})}
|
||||
</span>
|
||||
<div className="hide-scrollbar overflow-x-scroll">
|
||||
|
||||
@@ -5,6 +5,7 @@ import ConfirmButton from '@app/components/Common/ConfirmButton';
|
||||
import RequestModal from '@app/components/RequestModal';
|
||||
import StatusBadge from '@app/components/StatusBadge';
|
||||
import useDeepLinks from '@app/hooks/useDeepLinks';
|
||||
import useSettings from '@app/hooks/useSettings';
|
||||
import { Permission, useUser } from '@app/hooks/useUser';
|
||||
import globalMessages from '@app/i18n/globalMessages';
|
||||
import defineMessages from '@app/utils/defineMessages';
|
||||
@@ -294,6 +295,7 @@ interface RequestItemProps {
|
||||
}
|
||||
|
||||
const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
const settings = useSettings();
|
||||
const { ref, inView } = useInView({
|
||||
triggerOnce: true,
|
||||
});
|
||||
@@ -468,7 +470,14 @@ const RequestItem = ({ request, revalidateList }: RequestItemProps) => {
|
||||
<div className="card-field">
|
||||
<span className="card-field-name">
|
||||
{intl.formatMessage(messages.seasons, {
|
||||
seasonCount: request.seasons.length,
|
||||
seasonCount:
|
||||
(settings.currentSettings.enableSpecialEpisodes
|
||||
? title.seasons.length
|
||||
: title.seasons.filter(
|
||||
(season) => season.seasonNumber !== 0
|
||||
).length) === request.seasons.length
|
||||
? 0
|
||||
: request.seasons.length,
|
||||
})}
|
||||
</span>
|
||||
<div className="hide-scrollbar flex flex-nowrap overflow-x-scroll">
|
||||
|
||||
@@ -27,6 +27,7 @@ const messages = defineMessages(
|
||||
password: 'Password',
|
||||
tokenAuth: 'Token authentication',
|
||||
token: 'Token',
|
||||
priority: 'Priority',
|
||||
ntfysettingssaved: 'Ntfy notification settings saved successfully!',
|
||||
ntfysettingsfailed: 'Ntfy notification settings failed to save.',
|
||||
toastNtfyTestSending: 'Sending ntfy test notification…',
|
||||
@@ -34,6 +35,7 @@ const messages = defineMessages(
|
||||
toastNtfyTestFailed: 'Ntfy test notification failed to send.',
|
||||
validationNtfyUrl: 'You must provide a valid URL',
|
||||
validationNtfyTopic: 'You must provide a topic',
|
||||
validationPriorityRequired: 'You must provide a priority between 1 and 5',
|
||||
validationTypes: 'You must select at least one notification type',
|
||||
}
|
||||
);
|
||||
@@ -71,6 +73,14 @@ const NotificationsNtfy = () => {
|
||||
otherwise: Yup.string().nullable(),
|
||||
})
|
||||
.defined(intl.formatMessage(messages.validationNtfyTopic)),
|
||||
priority: Yup.number().when('enabled', {
|
||||
is: true,
|
||||
then: Yup.number()
|
||||
.min(1)
|
||||
.max(5)
|
||||
.required(intl.formatMessage(messages.validationPriorityRequired)),
|
||||
otherwise: Yup.number().nullable(),
|
||||
}),
|
||||
});
|
||||
|
||||
if (!data && !error) {
|
||||
@@ -90,6 +100,7 @@ const NotificationsNtfy = () => {
|
||||
password: data?.options.password,
|
||||
authMethodToken: data?.options.authMethodToken,
|
||||
token: data?.options.token,
|
||||
priority: data?.options.priority,
|
||||
}}
|
||||
validationSchema={NotificationsNtfySchema}
|
||||
onSubmit={async (values) => {
|
||||
@@ -106,6 +117,7 @@ const NotificationsNtfy = () => {
|
||||
password: values.password,
|
||||
authMethodToken: values.authMethodToken,
|
||||
token: values.token,
|
||||
priority: values.priority,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -157,6 +169,7 @@ const NotificationsNtfy = () => {
|
||||
password: values.password,
|
||||
authMethodToken: values.authMethodToken,
|
||||
token: values.token,
|
||||
priority: values.priority,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -313,6 +326,22 @@ const NotificationsNtfy = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-row">
|
||||
<label htmlFor="priority" className="text-label">
|
||||
{intl.formatMessage(messages.priority)}
|
||||
</label>
|
||||
<div className="form-input-area">
|
||||
<div className="form-input-field">
|
||||
<Field as="select" id="priority" name="priority">
|
||||
<option value={1}>Minimum</option>
|
||||
<option value={2}>Low</option>
|
||||
<option value={3}>Default</option>
|
||||
<option value={4}>High</option>
|
||||
<option value={5}>Urgent</option>
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<NotificationTypeSelector
|
||||
currentTypes={values.enabled ? values.types || 0 : 0}
|
||||
onUpdate={(newTypes) => {
|
||||
|
||||
@@ -49,12 +49,7 @@ const NotificationsPushover = () => {
|
||||
const { data: soundsData } = useSWR<PushoverSound[]>(
|
||||
data?.options.accessToken
|
||||
? `/api/v1/settings/notifications/pushover/sounds?token=${data.options.accessToken}`
|
||||
: null,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
shouldRetryOnError: false,
|
||||
}
|
||||
: null
|
||||
);
|
||||
|
||||
const NotificationsPushoverSchema = Yup.object().shape({
|
||||
|
||||
@@ -38,8 +38,6 @@ const messages = defineMessages('components.Settings.SettingsNetwork', {
|
||||
proxyBypassFilterTip:
|
||||
"Use ',' as a separator, and '*.' as a wildcard for subdomains",
|
||||
proxyBypassLocalAddresses: 'Bypass Proxy for Local Addresses',
|
||||
validationDnsCacheMinTtl: 'You must provide a valid minimum TTL',
|
||||
validationDnsCacheMaxTtl: 'You must provide a valid maximum TTL',
|
||||
validationProxyPort: 'You must provide a valid port',
|
||||
networkDisclaimer:
|
||||
'Network parameters from your container/system should be used instead of these settings. See the {docs} for more information.',
|
||||
@@ -66,20 +64,6 @@ const SettingsNetwork = () => {
|
||||
} = useSWR<NetworkSettings>('/api/v1/settings/network');
|
||||
|
||||
const NetworkSettingsSchema = Yup.object().shape({
|
||||
dnsCacheForceMinTtl: Yup.number().when('dnsCacheEnabled', {
|
||||
is: true,
|
||||
then: Yup.number()
|
||||
.typeError(intl.formatMessage(messages.validationDnsCacheMinTtl))
|
||||
.required(intl.formatMessage(messages.validationDnsCacheMinTtl))
|
||||
.min(0),
|
||||
}),
|
||||
dnsCacheForceMaxTtl: Yup.number().when('dnsCacheEnabled', {
|
||||
is: true,
|
||||
then: Yup.number()
|
||||
.typeError(intl.formatMessage(messages.validationDnsCacheMaxTtl))
|
||||
.required(intl.formatMessage(messages.validationDnsCacheMaxTtl))
|
||||
.min(0),
|
||||
}),
|
||||
proxyPort: Yup.number().when('proxyEnabled', {
|
||||
is: (proxyEnabled: boolean) => proxyEnabled,
|
||||
then: Yup.number().required(
|
||||
@@ -136,8 +120,8 @@ const SettingsNetwork = () => {
|
||||
trustProxy: values.trustProxy,
|
||||
dnsCache: {
|
||||
enabled: values.dnsCacheEnabled,
|
||||
forceMinTtl: Number(values.dnsCacheForceMinTtl),
|
||||
forceMaxTtl: Number(values.dnsCacheForceMaxTtl),
|
||||
forceMinTtl: values.dnsCacheForceMinTtl,
|
||||
forceMaxTtl: values.dnsCacheForceMaxTtl,
|
||||
},
|
||||
proxy: {
|
||||
enabled: values.proxyEnabled,
|
||||
@@ -297,7 +281,7 @@ const SettingsNetwork = () => {
|
||||
<Field
|
||||
id="dnsCacheForceMinTtl"
|
||||
name="dnsCacheForceMinTtl"
|
||||
type="number"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{errors.dnsCacheForceMinTtl &&
|
||||
@@ -321,7 +305,7 @@ const SettingsNetwork = () => {
|
||||
<Field
|
||||
id="dnsCacheForceMaxTtl"
|
||||
name="dnsCacheForceMaxTtl"
|
||||
type="number"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{errors.dnsCacheForceMaxTtl &&
|
||||
@@ -391,7 +375,7 @@ const SettingsNetwork = () => {
|
||||
<Field
|
||||
id="proxyPort"
|
||||
name="proxyPort"
|
||||
type="number"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
{errors.proxyPort &&
|
||||
|
||||
@@ -139,11 +139,7 @@ const StatusBadge = ({
|
||||
<div
|
||||
className={`
|
||||
absolute top-0 left-0 z-10 flex h-full bg-opacity-80 ${
|
||||
status === MediaStatus.DELETED
|
||||
? 'bg-red-600'
|
||||
: status === MediaStatus.PROCESSING
|
||||
? 'bg-indigo-500'
|
||||
: 'bg-green-500'
|
||||
status === MediaStatus.PROCESSING ? 'bg-indigo-500' : 'bg-green-500'
|
||||
} transition-all duration-200 ease-in-out
|
||||
`}
|
||||
style={{
|
||||
@@ -377,66 +373,11 @@ const StatusBadge = ({
|
||||
|
||||
case MediaStatus.DELETED:
|
||||
return (
|
||||
<Tooltip
|
||||
content={inProgress ? tooltipContent : mediaLinkDescription}
|
||||
className={`${
|
||||
inProgress && 'hidden max-h-96 w-96 overflow-y-auto sm:block'
|
||||
}`}
|
||||
tooltipConfig={{
|
||||
...(inProgress && { interactive: true, delayHide: 100 }),
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
badgeType="danger"
|
||||
href={mediaLink}
|
||||
className={`${
|
||||
inProgress &&
|
||||
'relative !bg-gray-700 !bg-opacity-80 !px-0 hover:!bg-gray-700'
|
||||
} overflow-hidden`}
|
||||
>
|
||||
{inProgress && badgeDownloadProgress}
|
||||
<div
|
||||
className={`relative z-20 flex items-center ${
|
||||
inProgress && 'px-2'
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
is4k ? messages.status4k : messages.status,
|
||||
{
|
||||
status: inProgress
|
||||
? intl.formatMessage(globalMessages.processing)
|
||||
: intl.formatMessage(globalMessages.deleted),
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
{inProgress && (
|
||||
<>
|
||||
{mediaType === 'tv' &&
|
||||
downloadItem[0].episode &&
|
||||
(downloadItem.length > 1 &&
|
||||
downloadItem.every(
|
||||
(item) =>
|
||||
item.downloadId &&
|
||||
item.downloadId === downloadItem[0].downloadId
|
||||
) ? (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonnumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
})}
|
||||
</span>
|
||||
) : (
|
||||
<span className="ml-1">
|
||||
{intl.formatMessage(messages.seasonepisodenumber, {
|
||||
seasonNumber: downloadItem[0].episode.seasonNumber,
|
||||
episodeNumber: downloadItem[0].episode.episodeNumber,
|
||||
})}
|
||||
</span>
|
||||
))}
|
||||
<Spinner className="ml-1 h-3 w-3" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Tooltip content={mediaLinkDescription}>
|
||||
<Badge badgeType="danger">
|
||||
{intl.formatMessage(is4k ? messages.status4k : messages.status, {
|
||||
status: intl.formatMessage(globalMessages.deleted),
|
||||
})}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -629,6 +629,7 @@
|
||||
"components.Settings.Notifications.NotificationsNtfy.ntfysettingsfailed": "Ntfy notification settings failed to save.",
|
||||
"components.Settings.Notifications.NotificationsNtfy.ntfysettingssaved": "Ntfy notification settings saved successfully!",
|
||||
"components.Settings.Notifications.NotificationsNtfy.password": "Password",
|
||||
"components.Settings.Notifications.NotificationsNtfy.priority": "Priority",
|
||||
"components.Settings.Notifications.NotificationsNtfy.toastNtfyTestFailed": "Ntfy test notification failed to send.",
|
||||
"components.Settings.Notifications.NotificationsNtfy.toastNtfyTestSending": "Sending ntfy test notification…",
|
||||
"components.Settings.Notifications.NotificationsNtfy.toastNtfyTestSuccess": "Ntfy test notification sent!",
|
||||
@@ -640,6 +641,7 @@
|
||||
"components.Settings.Notifications.NotificationsNtfy.usernamePasswordAuth": "Username + Password authentication",
|
||||
"components.Settings.Notifications.NotificationsNtfy.validationNtfyTopic": "You must provide a topic",
|
||||
"components.Settings.Notifications.NotificationsNtfy.validationNtfyUrl": "You must provide a valid URL",
|
||||
"components.Settings.Notifications.NotificationsNtfy.validationPriorityRequired": "You must provide a priority between 1 and 5",
|
||||
"components.Settings.Notifications.NotificationsNtfy.validationTypes": "You must select at least one notification type",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.accessToken": "Access Token",
|
||||
"components.Settings.Notifications.NotificationsPushbullet.accessTokenTip": "Create a token from your <PushbulletSettingsLink>Account Settings</PushbulletSettingsLink>",
|
||||
@@ -1022,8 +1024,6 @@
|
||||
"components.Settings.SettingsNetwork.toastSettingsSuccess": "Settings saved successfully!",
|
||||
"components.Settings.SettingsNetwork.trustProxy": "Enable Proxy Support",
|
||||
"components.Settings.SettingsNetwork.trustProxyTip": "Allow Seerr to correctly register client IP addresses behind a proxy",
|
||||
"components.Settings.SettingsNetwork.validationDnsCacheMaxTtl": "You must provide a valid maximum TTL",
|
||||
"components.Settings.SettingsNetwork.validationDnsCacheMinTtl": "You must provide a valid minimum TTL",
|
||||
"components.Settings.SettingsNetwork.validationProxyPort": "You must provide a valid port",
|
||||
"components.Settings.SettingsUsers.atLeastOneAuth": "At least one authentication method must be selected.",
|
||||
"components.Settings.SettingsUsers.defaultPermissions": "Default Permissions",
|
||||
|
||||
Reference in New Issue
Block a user