refactor: Remove unused state and simplify sensitive info handling in modals

This commit is contained in:
Aron Wiederkehr
2025-04-30 15:57:47 +02:00
parent 61646cf3fc
commit ee16181219
3 changed files with 13 additions and 100 deletions

View File

@@ -1,5 +1,5 @@
import { X, Copy, Tv2, Eye, EyeOff } from 'lucide-react';
import { useContext, useState } from 'react';
import { X, Copy, Tv2 } from 'lucide-react';
import { useContext } from 'react';
import { ToastContext } from './notifications/ToastContext';
interface TvPlaylistModalProps {
@@ -11,7 +11,6 @@ interface TvPlaylistModalProps {
function TvPlaylistModal({ isOpen, onClose, isAdmin = false }: TvPlaylistModalProps) {
const { addToast } = useContext(ToastContext);
const playlistUrl = `${import.meta.env.VITE_BACKEND_URL || window.location.origin}/api/channels/playlist`;
const [showHiddenInfo, setShowHiddenInfo] = useState(false);
if (!isOpen) return null;
@@ -73,26 +72,13 @@ function TvPlaylistModal({ isOpen, onClose, isAdmin = false }: TvPlaylistModalPr
<div className="mt-6 border-t border-gray-700 pt-4">
<div className="flex justify-between items-center mb-2">
<h3 className="text-lg font-medium">Admin Information</h3>
<button
onClick={() => setShowHiddenInfo(!showHiddenInfo)}
className="flex items-center space-x-1 text-blue-400 hover:text-blue-300"
>
{showHiddenInfo ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />}
<span>{showHiddenInfo ? 'Hide' : 'Show'} sensitive info</span>
</button>
</div>
<div className="bg-gray-900 rounded-lg p-4">
<p className="text-sm text-gray-300">
{showHiddenInfo ? (
<>
This playlist contains the actual stream URLs. You can share a link to the
application with non-admin users, and they will be able to watch the streams
without seeing the actual stream URLs.
</>
) : (
'Click "Show sensitive info" to view additional information about the playlist.'
)}
This playlist contains all stream URLs. You can share a link to the
application with other users, and they will be able to watch the streams
together with you.
</p>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useContext } from 'react';
import { Plus, Trash2, X, Eye, EyeOff } from 'lucide-react';
import { Plus, Trash2, X } from 'lucide-react';
import socketService from '../../services/SocketService';
import { CustomHeader, Channel, ChannelMode } from '../../types';
import CustomHeaderInput from './CustomHeaderInput';
@@ -16,7 +16,6 @@ function ChannelModal({ onClose, channel, isAdmin = false }: ChannelModalProps)
const [type, setType] = useState<'channel' | 'playlist'>('playlist');
const [isEditMode, setIsEditMode] = useState(false);
const [inputMethod, setInputMethod] = useState<'url' | 'text'>('url');
const [showSensitiveInfo, setShowSensitiveInfo] = useState(false);
const [name, setName] = useState('');
const [url, setUrl] = useState('');
@@ -173,32 +172,6 @@ function ChannelModal({ onClose, channel, isAdmin = false }: ChannelModalProps)
onClose();
};
// Obfuscate part of the URL to hide sensitive information
const getObfuscatedUrl = (fullUrl: string) => {
if (!fullUrl || showSensitiveInfo) return fullUrl;
try {
const url = new URL(fullUrl);
// Hide username and password in URL if present
if (url.username || url.password) {
return fullUrl.replace(/\/\/([^:@]+:[^@]+@)/g, '//***:***@');
}
// Hide tokens or API keys in query params
if (url.search && (url.search.includes('token') || url.search.includes('key') || url.search.includes('password') || url.search.includes('auth'))) {
return `${url.origin}${url.pathname}?***hidden***`;
}
return fullUrl;
} catch {
// If URL is malformed, just return a partially obfuscated string
if (fullUrl.length > 20) {
return fullUrl.substring(0, 10) + '...' + fullUrl.substring(fullUrl.length - 10);
}
return fullUrl;
}
};
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4">
<div className="bg-gray-800 rounded-lg w-full max-w-md">
@@ -255,25 +228,15 @@ function ChannelModal({ onClose, channel, isAdmin = false }: ChannelModalProps)
<label htmlFor="url" className="block text-sm font-medium">
Stream URL
</label>
<button
type="button"
onClick={() => setShowSensitiveInfo(!showSensitiveInfo)}
className="flex items-center text-xs text-blue-400 hover:text-blue-300"
title="Toggle URL visibility for privacy"
>
{showSensitiveInfo ? <EyeOff className="w-3 h-3 mr-1" /> : <Eye className="w-3 h-3 mr-1" />}
{showSensitiveInfo ? 'Hide' : 'Show'} URL
</button>
</div>
<input
type="url"
id="url"
value={showSensitiveInfo ? url : getObfuscatedUrl(url)}
value={url}
onChange={(e) => setUrl(e.target.value)}
className="w-full bg-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter stream URL"
required
readOnly={!showSensitiveInfo && url.length > 0}
/>
</div>
<div>
@@ -372,25 +335,15 @@ function ChannelModal({ onClose, channel, isAdmin = false }: ChannelModalProps)
M3U Text
</button>
</label>
<button
type="button"
onClick={() => setShowSensitiveInfo(!showSensitiveInfo)}
className="flex items-center text-xs text-blue-400 hover:text-blue-300"
title="Toggle URL visibility for privacy"
>
{showSensitiveInfo ? <EyeOff className="w-3 h-3 mr-1" /> : <Eye className="w-3 h-3 mr-1" />}
{showSensitiveInfo ? 'Hide' : 'Show'} URL
</button>
</div>
<input
type="url"
id="playlistUrl"
value={showSensitiveInfo ? playlistUrl : getObfuscatedUrl(playlistUrl)}
value={playlistUrl}
onChange={(e) => setPlaylistUrl(e.target.value)}
className="w-full bg-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
placeholder="Enter M3U playlist URL"
required={inputMethod === 'url'}
readOnly={!showSensitiveInfo && playlistUrl.length > 0}
/>
</div>
) : (
@@ -413,25 +366,15 @@ function ChannelModal({ onClose, channel, isAdmin = false }: ChannelModalProps)
M3U Text
</button>
</label>
<button
type="button"
onClick={() => setShowSensitiveInfo(!showSensitiveInfo)}
className="flex items-center text-xs text-blue-400 hover:text-blue-300"
title="Toggle content visibility for privacy"
>
{showSensitiveInfo ? <EyeOff className="w-3 h-3 mr-1" /> : <Eye className="w-3 h-3 mr-1" />}
{showSensitiveInfo ? 'Hide' : 'Show'} content
</button>
</div>
<textarea
id="playlistText"
value={showSensitiveInfo ? playlistText : (playlistText ? "#EXTM3U\n# Content hidden for privacy\n# Click 'Show content' to view or edit" : "")}
value={playlistText}
onChange={(e) => setPlaylistText(e.target.value)}
className="w-full bg-gray-700 rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 min-h-[200px] scroll-container overflow-y-auto"
placeholder="#EXTM3U..."
required={inputMethod === 'text'}
style={{ resize: 'none' }}
readOnly={!showSensitiveInfo && playlistText.length > 0}
/>
</div>
)}
@@ -506,15 +449,6 @@ function ChannelModal({ onClose, channel, isAdmin = false }: ChannelModalProps)
Custom Headers
</label>
<div className="flex items-center space-x-2">
<button
type="button"
onClick={() => setShowSensitiveInfo(!showSensitiveInfo)}
className="flex items-center text-xs text-blue-400 hover:text-blue-300"
title="Toggle headers visibility for privacy"
>
{showSensitiveInfo ? <EyeOff className="w-3 h-3 mr-1" /> : <Eye className="w-3 h-3 mr-1" />}
{showSensitiveInfo ? 'Hide' : 'Show'} headers
</button>
<button
type="button"
onClick={addHeader}
@@ -529,13 +463,9 @@ function ChannelModal({ onClose, channel, isAdmin = false }: ChannelModalProps)
{headers && headers.map((header, index) => (
<div key={index} className="flex items-center space-x-2">
<CustomHeaderInput
header={{
key: header.key,
value: showSensitiveInfo ? header.value : "***hidden***"
}}
header={header}
onKeyChange={(value) => updateHeader(index, 'key', value)}
onValueChange={(value) => updateHeader(index, 'value', value)}
readOnly={!showSensitiveInfo && header.value.length > 0}
/>
<button
type="button"

View File

@@ -4,10 +4,9 @@ interface CustomHeaderInputProps {
header: CustomHeader;
onKeyChange: (value: string) => void;
onValueChange: (value: string) => void;
readOnly?: boolean;
}
function CustomHeaderInput({ header, onKeyChange, onValueChange, readOnly = false }: CustomHeaderInputProps) {
function CustomHeaderInput({ header, onKeyChange, onValueChange }: CustomHeaderInputProps) {
return (
<div className="flex-1 grid grid-cols-2 gap-2">
<input
@@ -15,16 +14,14 @@ function CustomHeaderInput({ header, onKeyChange, onValueChange, readOnly = fals
value={header.key}
onChange={(e) => onKeyChange(e.target.value)}
placeholder="Header name"
className={`bg-gray-700 rounded-lg px-3 py-1.5 text-sm focus:outline-none ${!readOnly ? 'focus:ring-2 focus:ring-blue-500' : ''}`}
readOnly={readOnly}
className="bg-gray-700 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<input
type="text"
value={header.value}
onChange={(e) => onValueChange(e.target.value)}
placeholder="Header value"
className={`bg-gray-700 rounded-lg px-3 py-1.5 text-sm focus:outline-none ${!readOnly ? 'focus:ring-2 focus:ring-blue-500' : ''}`}
readOnly={readOnly}
className="bg-gray-700 rounded-lg px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
</div>
);