diff --git a/apps/epg/tasks.py b/apps/epg/tasks.py index d7963ec4..532b4de0 100644 --- a/apps/epg/tasks.py +++ b/apps/epg/tasks.py @@ -1,4 +1,5 @@ import logging +import gzip # <-- New import for gzip support from celery import shared_task from .models import EPGSource, EPGData, ProgramData from django.utils import timezone @@ -29,7 +30,16 @@ def fetch_xmltv(source): response = requests.get(source.url, timeout=30) response.raise_for_status() logger.debug("XMLTV data fetched successfully.") - root = ET.fromstring(response.content) + + # If the URL ends with '.gz', decompress the response content + if source.url.lower().endswith('.gz'): + logger.debug("Detected .gz file. Decompressing...") + decompressed_bytes = gzip.decompress(response.content) + xml_data = decompressed_bytes.decode('utf-8') + else: + xml_data = response.text + + root = ET.fromstring(xml_data) logger.debug("Parsed XMLTV XML content.") # Group programmes by their tvg_id from the XMLTV file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index b9b072dc..bf446dfe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -19,6 +19,7 @@ "eslint": "^8.57.1", "formik": "^2.4.6", "hls.js": "^1.5.20", + "lucide-react": "^0.479.0", "material-react-table": "^3.2.0", "mpegts.js": "^1.4.2", "planby": "^1.1.7", @@ -11701,6 +11702,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.479.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.479.0.tgz", + "integrity": "sha512-aBhNnveRhorBOK7uA4gDjgaf+YlHMdMhQ/3cupk6exM10hWlEU+2QtWYOfhXhjAsmdb6LeKR+NZnow4UxRRiTQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/m3u8-parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/m3u8-parser/-/m3u8-parser-7.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index b35b8885..c64bf59f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,6 +14,7 @@ "eslint": "^8.57.1", "formik": "^2.4.6", "hls.js": "^1.5.20", + "lucide-react": "^0.479.0", "material-react-table": "^3.2.0", "mpegts.js": "^1.4.2", "planby": "^1.1.7", diff --git a/frontend/src/App.js b/frontend/src/App.js index b40b1a37..334246ec 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -58,7 +58,7 @@ const App = () => { checkSuperuser(); }, []); - // Authentication check. + // Authentication check useEffect(() => { const checkAuth = async () => { const loggedIn = await initializeAuth(); @@ -72,7 +72,7 @@ const App = () => { checkAuth(); }, [initializeAuth, initData, setIsAuthenticated, logout]); - // If no superuser exists, show the initialization form. + // If no superuser exists, show the initialization form if (needsSuperuser) { return setNeedsSuperuser(false)} />; } @@ -90,6 +90,7 @@ const App = () => { /> + {/* Sidebar on the left */} { toggleDrawer={toggleDrawer} /> + {/* Main content area, no AppBar, so no marginTop */} - + {isAuthenticated ? ( <> @@ -144,6 +140,7 @@ const App = () => { + diff --git a/frontend/src/components/Sidebar.js b/frontend/src/components/Sidebar.js index 051515c7..cb0370bc 100644 --- a/frontend/src/components/Sidebar.js +++ b/frontend/src/components/Sidebar.js @@ -1,150 +1,179 @@ import React from 'react'; -import { Link, useLocation, useNavigate } from 'react-router-dom'; +import { Link, useLocation } from 'react-router-dom'; import { - List, - ListItem, - ListItemButton, - ListItemText, - ListItemIcon, - Box, - Divider, Drawer, - TextField, + Toolbar, + Box, + Typography, + Avatar, + List, + ListItemButton, + ListItemIcon, + ListItemText, } from '@mui/material'; import { - Tv as TvIcon, - CalendarMonth as CalendarMonthIcon, - VideoFile as VideoFileIcon, - LiveTv as LiveTvIcon, - PlaylistPlay as PlaylistPlayIcon, - Settings as SettingsIcon, - Logout as LogoutIcon, -} from '@mui/icons-material'; + ListOrdered, + Play, + Database, + SlidersHorizontal, + LayoutGrid, + Settings as LucideSettings, +} from 'lucide-react'; import logo from '../images/logo.png'; -import useAuthStore from '../store/auth'; -import useSettingsStore from '../store/settings'; -const items = [ - { text: 'Channels', icon: , route: '/channels' }, - { text: 'M3U', icon: , route: '/m3u' }, - { text: 'EPG', icon: , route: '/epg' }, +const navItems = [ + { label: 'Channels', icon: , path: '/channels' }, + { label: 'M3U', icon: , path: '/m3u' }, + { label: 'EPG', icon: , path: '/epg' }, { - text: 'Stream Profiles', - icon: , - route: '/stream-profiles', + label: 'Stream Profiles', + icon: , + path: '/stream-profiles', }, - { text: 'TV Guide', icon: , route: '/guide' }, - { text: 'Settings', icon: , route: '/settings' }, + { label: 'TV Guide', icon: , path: '/guide' }, + { label: 'Settings', icon: , path: '/settings' }, ]; -const Sidebar = ({ open, miniDrawerWidth, drawerWidth, toggleDrawer }) => { +const Sidebar = ({ open, drawerWidth, miniDrawerWidth, toggleDrawer }) => { const location = useLocation(); - const { isAuthenticated, logout } = useAuthStore(); - const { - environment: { public_ip, country_code, country_name }, - } = useSettingsStore(); - const navigate = useNavigate(); - - const onLogout = () => { - logout(); - navigate('/login'); - }; return ( - - - + + {open ? ( + + Dispatcharr Logo + + Dispatcharr + + + ) : ( + Dispatcharr Logo + )} + + + + {navItems.map((item) => { + const isActive = location.pathname.startsWith(item.path); + return ( - logo - {open && ( - - )} - - - - - - - {items.map((item) => ( - - - {item.icon} - {open && } - - - ))} - - - - {isAuthenticated && ( - - - - - - + + {item.icon} - - - - - {open && ( - - {/* Public IP + optional flag */} - - - {/* If we have a country code, show a small flag */} - {country_code && ( - {country_name )} - - )} - - )} + + ); + })} + + + + + + {open && ( + + John Doe + + )} + ); }; diff --git a/frontend/src/helpers/table.js b/frontend/src/helpers/table.js index 9f898843..33786af2 100644 --- a/frontend/src/helpers/table.js +++ b/frontend/src/helpers/table.js @@ -1,3 +1,5 @@ +// frontend/src/helpers/table.js + export default { defaultProperties: { enableGlobalFilter: false, @@ -16,21 +18,34 @@ export default { }, muiTableBodyCellProps: { sx: { - pt: 0, - pb: 0, + padding: '6px', + borderColor: '#444', + color: '#E0E0E0', + fontSize: '0.85rem', }, }, muiTableHeadCellProps: { sx: { - pt: 0, - pb: 0, + padding: '6px', + color: '#CFCFCF', + backgroundColor: '#383A3F', + borderColor: '#444', + fontWeight: 600, + fontSize: '0.8rem', }, }, muiTableBodyProps: { sx: { - //stripe the rows, make odd rows a darker color - '& tr:nth-of-type(odd) > td': { - // backgroundColor: '#f5f5f5', + // Subtle row striping + '& tr:nth-of-type(odd)': { + backgroundColor: '#2F3034', + }, + '& tr:nth-of-type(even)': { + backgroundColor: '#333539', + }, + // Row hover effect + '& tr:hover td': { + backgroundColor: '#3B3D41', }, }, }, diff --git a/frontend/src/images/dispatcharr.svg b/frontend/src/images/dispatcharr.svg new file mode 100644 index 00000000..3ddd8e2b --- /dev/null +++ b/frontend/src/images/dispatcharr.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/images/logo.png b/frontend/src/images/logo.png index afadd08e..99c3c19f 100644 Binary files a/frontend/src/images/logo.png and b/frontend/src/images/logo.png differ diff --git a/frontend/src/index.css b/frontend/src/index.css index ec2585e8..06515741 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,3 +1,5 @@ +/* frontend/src/index.css */ + body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', @@ -5,9 +7,25 @@ body { sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + background-color: #2E2F34; /* Ensure the global background is dark */ + color: #ffffff; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } + +/* Example scrollbars - optional, to match a dark theme. */ +::-webkit-scrollbar { + width: 8px; +} +::-webkit-scrollbar-track { + background: #3B3C41; +} +::-webkit-scrollbar-thumb { + background: #555; +} +::-webkit-scrollbar-thumb:hover { + background: #777; +} diff --git a/frontend/src/theme.js b/frontend/src/theme.js index 258d415a..9c70a255 100644 --- a/frontend/src/theme.js +++ b/frontend/src/theme.js @@ -1,21 +1,65 @@ +// frontend/src/theme.js import { createTheme } from '@mui/material/styles'; const theme = createTheme({ palette: { mode: 'dark', + background: { + default: '#2B2C30', // Dark background + paper: '#333539', // Slightly lighter panel background + }, primary: { - main: '#495057', - contrastText: '#ffffff', // Ensure text is visible on primary color + // Adjust accent color if your Figma calls for a different highlight + main: '#4A90E2', + contrastText: '#FFFFFF', + }, + secondary: { + main: '#F5A623', + contrastText: '#FFFFFF', + }, + text: { + primary: '#FFFFFF', + secondary: '#C3C3C3', + }, + }, + typography: { + fontFamily: ['Roboto', 'Helvetica', 'Arial', 'sans-serif'].join(','), + // Example typography tweaks + h6: { + fontWeight: 500, + fontSize: '0.95rem', + }, + body1: { + fontSize: '0.875rem', }, }, components: { MuiButton: { styleOverrides: { root: { - // textTransform: 'none', // Disable uppercase on buttons + borderRadius: 4, + textTransform: 'none', + fontWeight: 500, }, }, }, + MuiDrawer: { + styleOverrides: { + paper: { + backgroundColor: '#333539', + color: '#FFFFFF', + }, + }, + }, + // We remove the AppBar override since we won't be using it in App.js anymore + MuiAppBar: { + styleOverrides: { + root: { + backgroundColor: '#2B2C30', + }, + }, + }, + // Feel free to override more MUI components as needed... }, });