mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-07-19 10:44:18 +01:00
fix(review): effective streamystats gate, honest refresh toast, locked jellyseerr url, i18n admin notice
This commit is contained in:
@@ -28,7 +28,9 @@ export default function MarlinSearchPage() {
|
|||||||
|
|
||||||
const searchEngineLocked = pluginSettings?.searchEngine?.locked === true;
|
const searchEngineLocked = pluginSettings?.searchEngine?.locked === true;
|
||||||
const marlinUrlLocked = pluginSettings?.marlinServerUrl?.locked === true;
|
const marlinUrlLocked = pluginSettings?.marlinServerUrl?.locked === true;
|
||||||
const hasStreamystats = !!pluginSettings?.streamyStatsServerUrl?.value;
|
// Effective (user/admin merged) URL, same source the search screen uses —
|
||||||
|
// the raw plugin value misses a user-configured Streamystats.
|
||||||
|
const hasStreamystats = !!settings?.streamyStatsServerUrl;
|
||||||
|
|
||||||
const onSave = (val: string) => {
|
const onSave = (val: string) => {
|
||||||
updateSettings({
|
updateSettings({
|
||||||
@@ -74,6 +76,9 @@ export default function MarlinSearchPage() {
|
|||||||
"home.settings.plugins.marlin_search.enable_marlin_search",
|
"home.settings.plugins.marlin_search.enable_marlin_search",
|
||||||
)}
|
)}
|
||||||
disabledByAdmin={searchEngineLocked}
|
disabledByAdmin={searchEngineLocked}
|
||||||
|
// Streamystats owns the search engine while configured — block the
|
||||||
|
// row tap too, not just the Switch, so it can't force "Jellyfin".
|
||||||
|
disabled={hasStreamystats}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
updateSettings({ searchEngine: "Jellyfin" });
|
updateSettings({ searchEngine: "Jellyfin" });
|
||||||
queryClient.invalidateQueries({ queryKey: ["search"] });
|
queryClient.invalidateQueries({ queryKey: ["search"] });
|
||||||
|
|||||||
@@ -13,8 +13,15 @@ export default function PluginsPage() {
|
|||||||
const { refreshStreamyfinPluginSettings } = useSettings();
|
const { refreshStreamyfinPluginSettings } = useSettings();
|
||||||
|
|
||||||
const handleRefreshFromServer = useCallback(async () => {
|
const handleRefreshFromServer = useCallback(async () => {
|
||||||
await refreshStreamyfinPluginSettings();
|
// Returns undefined when the API call fails (handled internally).
|
||||||
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
|
const refreshed = await refreshStreamyfinPluginSettings();
|
||||||
|
if (refreshed) {
|
||||||
|
toast.success(t("home.settings.plugins.streamystats.toasts.refreshed"));
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
t("home.settings.plugins.streamystats.toasts.refresh_failed"),
|
||||||
|
);
|
||||||
|
}
|
||||||
}, [refreshStreamyfinPluginSettings, t]);
|
}, [refreshStreamyfinPluginSettings, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import type { PropsWithChildren, ReactNode } from "react";
|
import type { PropsWithChildren, ReactNode } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { TouchableOpacity, View, type ViewProps } from "react-native";
|
import { TouchableOpacity, View, type ViewProps } from "react-native";
|
||||||
import { Text } from "../common/Text";
|
import { Text } from "../common/Text";
|
||||||
|
|
||||||
@@ -32,7 +33,10 @@ export const ListItem: React.FC<PropsWithChildren<Props>> = ({
|
|||||||
disabledByAdmin = false,
|
disabledByAdmin = false,
|
||||||
...viewProps
|
...viewProps
|
||||||
}) => {
|
}) => {
|
||||||
const effectiveSubtitle = disabledByAdmin ? "Disabled by admin" : subtitle;
|
const { t } = useTranslation();
|
||||||
|
const effectiveSubtitle = disabledByAdmin
|
||||||
|
? t("home.settings.disabled_by_admin")
|
||||||
|
: subtitle;
|
||||||
const isDisabled = disabled || disabledByAdmin;
|
const isDisabled = disabled || disabledByAdmin;
|
||||||
if (onPress)
|
if (onPress)
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -33,22 +33,25 @@ export const JellyseerrSettings = () => {
|
|||||||
string | undefined
|
string | undefined
|
||||||
>(settings?.jellyseerrServerUrl || undefined);
|
>(settings?.jellyseerrServerUrl || undefined);
|
||||||
|
|
||||||
|
// When the URL is admin-pinned, ignore any locally-typed value so the login
|
||||||
|
// targets the same server the (read-only) input displays.
|
||||||
|
const effectiveServerUrl = urlLocked
|
||||||
|
? settings?.jellyseerrServerUrl
|
||||||
|
: jellyseerrServerUrl || settings?.jellyseerrServerUrl;
|
||||||
|
|
||||||
const loginToJellyseerrMutation = useMutation({
|
const loginToJellyseerrMutation = useMutation({
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (!jellyseerrServerUrl && !settings?.jellyseerrServerUrl)
|
if (!effectiveServerUrl) throw new Error("Missing server url");
|
||||||
throw new Error("Missing server url");
|
|
||||||
if (!user?.Name)
|
if (!user?.Name)
|
||||||
throw new Error("Missing required information for login");
|
throw new Error("Missing required information for login");
|
||||||
const jellyseerrTempApi = new JellyseerrApi(
|
const jellyseerrTempApi = new JellyseerrApi(effectiveServerUrl);
|
||||||
jellyseerrServerUrl || settings.jellyseerrServerUrl || "",
|
|
||||||
);
|
|
||||||
const testResult = await jellyseerrTempApi.test();
|
const testResult = await jellyseerrTempApi.test();
|
||||||
if (!testResult.isValid) throw new Error("Invalid server url");
|
if (!testResult.isValid) throw new Error("Invalid server url");
|
||||||
return jellyseerrTempApi.login(user.Name, jellyseerrPassword || "");
|
return jellyseerrTempApi.login(user.Name, jellyseerrPassword || "");
|
||||||
},
|
},
|
||||||
onSuccess: (user) => {
|
onSuccess: (user) => {
|
||||||
setJellyseerrUser(user);
|
setJellyseerrUser(user);
|
||||||
updateSettings({ jellyseerrServerUrl });
|
updateSettings({ jellyseerrServerUrl: effectiveServerUrl });
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
toast.error(t("jellyseerr.failed_to_login"));
|
toast.error(t("jellyseerr.failed_to_login"));
|
||||||
@@ -149,7 +152,7 @@ export const JellyseerrSettings = () => {
|
|||||||
/>
|
/>
|
||||||
{urlLocked && (
|
{urlLocked && (
|
||||||
<Text className='text-xs text-red-600 mb-2'>
|
<Text className='text-xs text-red-600 mb-2'>
|
||||||
Disabled by admin
|
{t("home.settings.disabled_by_admin")}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -118,6 +118,7 @@
|
|||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"settings_title": "Settings",
|
"settings_title": "Settings",
|
||||||
|
"disabled_by_admin": "Disabled by admin",
|
||||||
"log_out_button": "Log out",
|
"log_out_button": "Log out",
|
||||||
"switch_user": {
|
"switch_user": {
|
||||||
"title": "Switch user",
|
"title": "Switch user",
|
||||||
@@ -370,6 +371,7 @@
|
|||||||
"toasts": {
|
"toasts": {
|
||||||
"saved": "Saved",
|
"saved": "Saved",
|
||||||
"refreshed": "Settings refreshed from server",
|
"refreshed": "Settings refreshed from server",
|
||||||
|
"refresh_failed": "Couldn't refresh settings from server",
|
||||||
"disabled": "Streamystats disabled"
|
"disabled": "Streamystats disabled"
|
||||||
},
|
},
|
||||||
"refresh_from_server": "Refresh settings from server"
|
"refresh_from_server": "Refresh settings from server"
|
||||||
|
|||||||
Reference in New Issue
Block a user