Spaces:
Paused
Paused
| import { useStatus } from '@/context/status'; | |
| import { PageWrapper } from '../shared/page-wrapper'; | |
| import { useState, useEffect } from 'react'; | |
| import * as constants from '../../../../core/src/utils/constants'; | |
| import { useUserData, useParentInheritance } from '@/context/userData'; | |
| import { InheritedBadge } from '../shared/inherited-badge'; | |
| import { Switch } from '../ui/switch'; | |
| import { Select } from '../ui/select'; | |
| import { Combobox } from '../ui/combobox'; | |
| import { SettingsCard } from '../shared/settings-card'; | |
| import { TextInput } from '../ui/text-input'; | |
| import { toast } from 'sonner'; | |
| import { Button } from '../ui/button'; | |
| import MarkdownLite from '../shared/markdown-lite'; | |
| import { PageControls } from '../shared/page-controls'; | |
| import { PasswordInput } from '../ui/password-input'; | |
| import { useMode } from '@/context/mode'; | |
| type ProxyServiceId = keyof typeof constants.PROXY_SERVICE_DETAILS; | |
| type ProxyConfig = { | |
| enabled: boolean; | |
| id: ProxyServiceId; | |
| url: string; | |
| credentials: string; | |
| publicIp?: string; | |
| proxiedAddons?: string[]; | |
| proxiedServices?: string[]; | |
| }; | |
| export function ProxyMenu() { | |
| return ( | |
| <> | |
| <PageWrapper className="space-y-4 p-4 sm:p-8"> | |
| <Content /> | |
| </PageWrapper> | |
| </> | |
| ); | |
| } | |
| function Content() { | |
| const { status } = useStatus(); | |
| const { userData, setUserData } = useUserData(); | |
| const { mode } = useMode(); | |
| const { isInherited, hasParent } = useParentInheritance(); | |
| const details = constants.PROXY_SERVICE_DETAILS; | |
| // Effect to initialize values from userData/defaults/forced | |
| // Generate options for proxy service select | |
| const proxyOptions = Object.entries(details).map(([id, detail]) => ({ | |
| label: detail.name, | |
| value: id, | |
| })); | |
| // Generate options for services multi-select | |
| const serviceOptions = [ | |
| ...Object.values(constants.SERVICE_DETAILS).map((service) => ({ | |
| label: service.name, | |
| value: service.id, | |
| textValue: service.name, | |
| })), | |
| { | |
| label: 'None', | |
| value: 'none', | |
| textValue: 'None', | |
| }, | |
| ]; | |
| const addonOptions = userData.presets.map((preset) => { | |
| return { | |
| label: preset.options.name || preset.type, | |
| value: preset.instanceId, | |
| textValue: preset.options.name, | |
| }; | |
| }); | |
| // lues are forced | |
| const isForced = status?.settings.forced.proxy; | |
| const isProxyForced = isForced?.enabled !== null; | |
| const isUrlForced = isForced?.url !== null; | |
| const isPublicUrlForced = isForced?.publicUrl !== null; | |
| const isIdForced = isForced?.id !== null; | |
| const isPublicIpForced = isForced?.publicIp !== null; | |
| const isCredentialsForced = isForced?.credentials !== null; | |
| const isServicesForced = isForced?.proxiedServices !== null; | |
| const isProxiedAddonsDisabled = isForced?.disableProxiedAddons; | |
| const selectedProxyDetails = userData.proxy?.id | |
| ? details[userData.proxy.id] | |
| : undefined; | |
| return ( | |
| <> | |
| <div className="flex items-center w-full"> | |
| <div> | |
| <div className="flex items-center gap-2"> | |
| <h2>Proxy</h2> | |
| {hasParent && isInherited('proxy') && ( | |
| <InheritedBadge section="proxy" /> | |
| )} | |
| </div> | |
| <p className="text-[--muted]"> | |
| Configure a proxy for your streams to bypass IP restrictions or | |
| improve compatibility | |
| </p> | |
| </div> | |
| <div className="hidden lg:block lg:ml-auto"> | |
| <PageControls /> | |
| </div> | |
| </div> | |
| <div className="space-y-2"> | |
| <SettingsCard> | |
| <Switch | |
| side="right" | |
| label="Enable" | |
| value={userData.proxy?.enabled ?? false} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, enabled: v }, | |
| })); | |
| }} | |
| disabled={isProxyForced} | |
| /> | |
| </SettingsCard> | |
| <SettingsCard> | |
| <div className="space-y-2"> | |
| <Select | |
| label="Proxy Service" | |
| value={userData.proxy?.id} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, id: v as ProxyServiceId }, | |
| })); | |
| }} | |
| options={proxyOptions} | |
| disabled={isIdForced || !userData.proxy?.enabled} | |
| /> | |
| {selectedProxyDetails && ( | |
| <p className="text-[--muted] text-sm"> | |
| <MarkdownLite>{selectedProxyDetails.description}</MarkdownLite> | |
| </p> | |
| )} | |
| </div> | |
| {userData.proxy?.id !== 'builtin' && ( | |
| <div className="space-y-2"> | |
| <PasswordInput | |
| label="URL" | |
| value={userData.proxy?.url ?? ''} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, url: v }, | |
| })); | |
| }} | |
| placeholder="Enter proxy URL" | |
| disabled={isUrlForced || !userData.proxy?.enabled} | |
| /> | |
| <p className="text-[--muted] text-sm"> | |
| The URL of your hosted proxy service. | |
| </p> | |
| </div> | |
| )} | |
| {userData.proxy?.id !== 'builtin' && ( | |
| <div className="space-y-2"> | |
| <PasswordInput | |
| label="Public URL (optional)" | |
| value={userData.proxy?.publicUrl ?? ''} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, publicUrl: v }, | |
| })); | |
| }} | |
| placeholder="Enter proxy public URL" | |
| disabled={isPublicUrlForced || !userData.proxy?.enabled} | |
| /> | |
| <p className="text-[--muted] text-sm"> | |
| The public URL of your hosted proxy service. Provide this only | |
| if you want to use a local URL for requests but a publicly | |
| accessible URL is needed for streams. e.g. setting http:// | |
| {userData.proxy?.id | |
| ? userData.proxy.id === 'stremthru' | |
| ? 'stremthru:8080' | |
| : 'mediaflow-proxy:8888' | |
| : 'mediaflow-proxy:8888'} | |
| as the URL above but then using https:// | |
| {userData.proxy?.id | |
| ? userData.proxy.id === 'stremthru' | |
| ? 'stremthru.yourdomain.com' | |
| : 'mediaflow-proxy.yourdomain.com' | |
| : 'mediaflow-proxy.yourdomain.com'} | |
| as the public URL. | |
| </p> | |
| </div> | |
| )} | |
| <div className="space-y-2"> | |
| <PasswordInput | |
| label="Credentials" | |
| value={userData.proxy?.credentials ?? ''} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, credentials: v }, | |
| })); | |
| }} | |
| placeholder="Enter proxy credentials" | |
| disabled={isCredentialsForced || !userData.proxy?.enabled} | |
| /> | |
| {selectedProxyDetails && ( | |
| <p className="text-[--muted] text-sm"> | |
| <MarkdownLite> | |
| {selectedProxyDetails.credentialDescription} | |
| </MarkdownLite> | |
| </p> | |
| )} | |
| </div> | |
| {mode === 'pro' && ( | |
| <div className="space-y-2"> | |
| <TextInput | |
| label="Public IP" | |
| value={userData.proxy?.publicIp ?? ''} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, publicIp: v || undefined }, | |
| })); | |
| }} | |
| placeholder="Enter public IP" | |
| disabled={isPublicIpForced || !userData.proxy?.enabled} | |
| /> | |
| <p className="text-[--muted] text-sm"> | |
| Configure this only when running {selectedProxyDetails?.name}{' '} | |
| locally with a proxy service. Leave empty if{' '} | |
| {selectedProxyDetails?.name} is configured locally without a | |
| proxy server or if it's hosted on a remote server. | |
| </p> | |
| </div> | |
| )} | |
| </SettingsCard> | |
| {mode === 'pro' && ( | |
| <SettingsCard | |
| title="Proxy Controls" | |
| description="Optionally, specify services and addons that should be proxied. These options are applied conjunctively." | |
| > | |
| <div className="space-y-2"> | |
| <Combobox | |
| label="Proxied Services" | |
| value={userData.proxy?.proxiedServices ?? []} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, proxiedServices: v }, | |
| })); | |
| }} | |
| options={serviceOptions} | |
| placeholder="Select services to proxy" | |
| multiple={true} | |
| disabled={isServicesForced || !userData.proxy?.enabled} | |
| emptyMessage="No services available" | |
| /> | |
| <p className="text-[--muted] text-sm"> | |
| Only streams (that are detected to be) from these services will | |
| be proxied. Select None to enable proxying of streams that are | |
| not detected to be from a service. | |
| </p> | |
| </div> | |
| <div className="space-y-2"> | |
| <Combobox | |
| label="Proxied Addons" | |
| value={userData.proxy?.proxiedAddons ?? []} | |
| onValueChange={(v) => { | |
| setUserData((prev) => ({ | |
| ...prev, | |
| proxy: { ...prev.proxy, proxiedAddons: v }, | |
| })); | |
| }} | |
| options={addonOptions} | |
| placeholder="Select addons to proxy" | |
| multiple={true} | |
| disabled={isProxiedAddonsDisabled || !userData.proxy?.enabled} | |
| emptyMessage="No addons available" | |
| /> | |
| <p className="text-[--muted] text-sm"> | |
| Only streams from these addons will be proxied | |
| </p> | |
| </div> | |
| </SettingsCard> | |
| )} | |
| </div> | |
| </> | |
| ); | |
| } | |