From 195b48082ca4959e9bb8894539430e5100ee1bf4 Mon Sep 17 00:00:00 2001 From: Fergal Moran Date: Mon, 11 Apr 2022 23:03:11 +0100 Subject: [PATCH] Creds sending done --- backend/server/api.py | 21 ++- backend/server/lib/xtream.py | 3 + frontend/package.json | 1 + frontend/public/images/unknown-stream.svg | 115 +++++++++++++++ frontend/src/App.tsx | 7 +- frontend/src/assets/images/tv.png | Bin 0 -> 19938 bytes frontend/src/components/index.ts | 10 +- .../components/server-details.component.tsx | 107 ++++++++++++++ .../sidebar/mobile-sidebar.component.tsx | 21 ++- .../sidebar/sidebar-content.component.tsx | 121 ++++++++-------- .../components/widgets/button.component.tsx | 12 +- .../widgets/helper-text.component.tsx | 41 ++++++ .../widgets/image-fallback.component.tsx | 18 +++ frontend/src/components/widgets/index.ts | 14 +- .../components/widgets/input.component.tsx | 12 +- frontend/src/containers/layout.container.tsx | 3 +- frontend/src/containers/main.container.tsx | 2 +- frontend/src/index.css | 47 ++++++ frontend/src/pages/channel.page.tsx | 137 +++++++----------- frontend/src/pages/onboarding.page.tsx | 33 +---- frontend/src/services/api.service.ts | 23 +++ frontend/yarn.lock | 5 + 22 files changed, 559 insertions(+), 194 deletions(-) create mode 100644 frontend/public/images/unknown-stream.svg create mode 100644 frontend/src/assets/images/tv.png create mode 100644 frontend/src/components/server-details.component.tsx create mode 100644 frontend/src/components/widgets/helper-text.component.tsx create mode 100644 frontend/src/components/widgets/image-fallback.component.tsx diff --git a/backend/server/api.py b/backend/server/api.py index f0479ee..5e2a880 100644 --- a/backend/server/api.py +++ b/backend/server/api.py @@ -1,11 +1,10 @@ import logging import uvicorn -from fastapi import FastAPI, Request +from fastapi import FastAPI, Request, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse -from server import config from server.lib.streamer import Streamer from server.lib.xtream import XTream @@ -21,8 +20,6 @@ origins = [ "https://streams.fergl.ie", "http://127.0.0.1:35729", "http://localhost:35729", - "https://bitmovin.com", - "https://players.akamai.com", ] app.add_middleware( @@ -47,6 +44,22 @@ def __get_provider(request: Request): ) +@app.get("/validate") +async def validate_crendentials(request: Request, response: Response): + try: + provider = __get_provider(request) + categories = provider.get_categories().json() + if type(categories) is list: + return {"status": "accepted"} + except ValueError as e: + logger.error(e) + except Exception as e: + logger.error(e) + + response.status_code = 401 + return {"status": "denied"} + + @app.get("/channels") async def channels(request: Request): provider = __get_provider(request) diff --git a/backend/server/lib/xtream.py b/backend/server/lib/xtream.py index aaeaced..1b18ab5 100644 --- a/backend/server/lib/xtream.py +++ b/backend/server/lib/xtream.py @@ -18,6 +18,9 @@ class XTream: _cache = Cache() def __init__(self, server, username, password): + if not (server and username and password): + raise ValueError("XTream: must specify server, username and password") + self._server = server self._username = username self._password = password diff --git a/frontend/package.json b/frontend/package.json index b72d0cd..0a9b8a5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,6 +16,7 @@ "react-dom": "^17.0.2", "react-focus-lock": "^2.8.1", "react-helmet": "^6.1.0", + "react-hook-form": "^7.29.0", "react-icons": "^4.3.1", "react-router-dom": "6", "react-scripts": "5.0.0", diff --git a/frontend/public/images/unknown-stream.svg b/frontend/public/images/unknown-stream.svg new file mode 100644 index 0000000..1adf606 --- /dev/null +++ b/frontend/public/images/unknown-stream.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 70bc1f3..746801e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -8,9 +8,12 @@ function App() { {localStorage.getItem("server") ? ( - } /> + <> + } /> + } /> + ) : ( - } /> + } /> )} diff --git a/frontend/src/assets/images/tv.png b/frontend/src/assets/images/tv.png new file mode 100644 index 0000000000000000000000000000000000000000..4a75dc85a1b263b90b142442fbb076ff0c6ae451 GIT binary patch literal 19938 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 literal 0 HcmV?d00001 diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index 056bd5a..841ac6e 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -3,5 +3,13 @@ import Navbar from "./navbar.component"; import Sidebar from "./sidebar/sidebar-content.component"; import EPGComponent from "./epg.component"; import ThemedSuspence from "./themed-suspence.component"; +import ServerDetails from "./server-details.component"; -export { Navbar, Sidebar, HLSPlayer, EPGComponent, ThemedSuspence }; +export { + Navbar, + Sidebar, + HLSPlayer, + EPGComponent, + ThemedSuspence, + ServerDetails, +}; diff --git a/frontend/src/components/server-details.component.tsx b/frontend/src/components/server-details.component.tsx new file mode 100644 index 0000000..0d3114e --- /dev/null +++ b/frontend/src/components/server-details.component.tsx @@ -0,0 +1,107 @@ +import React from "react"; +import { Input, Label, Button, HelperText } from "./widgets"; +import { useForm } from "react-hook-form"; +import { BiRocket } from "react-icons/bi"; +import { ApiService } from "../services"; +import { toast } from "react-toastify"; + +const ServerDetails = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const onSubmit = async (data: any) => { + console.log("server-details.component", "onSubmit", errors); + const validated = await ApiService.validateCredentials( + data.server, + data.username, + data.password + ); + if (validated) { + localStorage.setItem("server", JSON.stringify(data)); + window.location.reload(); + } else { + toast.error("FUCK YOU INVALID CREDENTIALS"); + } + }; + return ( +
+

+ XTream Codes Details +

+
+ + + + + + + +
+
+
+ ); +}; + +export default ServerDetails; diff --git a/frontend/src/components/sidebar/mobile-sidebar.component.tsx b/frontend/src/components/sidebar/mobile-sidebar.component.tsx index 5bacf8e..f06629a 100644 --- a/frontend/src/components/sidebar/mobile-sidebar.component.tsx +++ b/frontend/src/components/sidebar/mobile-sidebar.component.tsx @@ -1,14 +1,25 @@ +import { Transition } from "@headlessui/react"; import React from "react"; import { SidebarContext } from "../../context"; import SidebarContent from "./sidebar-content.component"; const MobileSidebar = () => { const { isSidebarOpen } = React.useContext(SidebarContext); - return isSidebarOpen ? ( - - ) : null; + return ( + + + + ); }; export default MobileSidebar; diff --git a/frontend/src/components/sidebar/sidebar-content.component.tsx b/frontend/src/components/sidebar/sidebar-content.component.tsx index 1f05483..eca672b 100644 --- a/frontend/src/components/sidebar/sidebar-content.component.tsx +++ b/frontend/src/components/sidebar/sidebar-content.component.tsx @@ -1,79 +1,80 @@ import React from "react"; -import { NavLink, Route } from "react-router-dom"; +import { NavLink } from "react-router-dom"; import { Channel } from "../../models/channel"; import { ApiService } from "../../services"; const SidebarContent = () => { const [channels, setChannels] = React.useState([]); - const [filteredChannels, setFilteredChannels] = React.useState([]); + // const [filteredChannels, setFilteredChannels] = React.useState([]); React.useEffect(() => { const fetchChannels = async () => { const res = await ApiService.getChannels(); if (res) { setChannels(res); - setFilteredChannels(res); } }; - fetchChannels().catch(console.error); + fetchChannels(); }, []); - const _searchChannels = ($event: React.ChangeEvent) => { - const searchString = $event.target.value; - if (searchString) { - const filteredChannels = channels.filter((c) => { - const result = c.category_name - .toLowerCase() - .includes(searchString.toLowerCase()); - console.log( - "sidebar.component", - `Category Name: ${c.category_name}`, - `Search String: ${searchString}` - ); - console.log("sidebar.component", "Result", result); - return result; - }); - setFilteredChannels(filteredChannels); - } else { - setFilteredChannels(channels); - } - }; + // const _searchChannels = ($event: React.ChangeEvent) => { + // const searchString = $event.target.value; + // if (searchString) { + // const filteredChannels = channels.filter((c) => { + // const result = c.category_name + // .toLowerCase() + // .includes(searchString.toLowerCase()); + // console.log( + // "sidebar.component", + // `Category Name: ${c.category_name}`, + // `Search String: ${searchString}` + // ); + // console.log("sidebar.component", "Result", result); + // return result; + // }); + // setFilteredChannels(filteredChannels); + // } else { + // setFilteredChannels(channels); + // } + // }; return ( -
- - Xtreamium - -
    - {filteredChannels.map((channel: Channel) => ( -
  • - - `inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200 ${ - isActive && "text-gray-800 dark:text-gray-100" - }` - } - children={({ isActive }) => { - return ( - <> - {isActive && ( - - )} - {/*
  • - ))} -
-
+ channels && ( +
+ + Xtreamium + +
    + {channels.map((channel: Channel) => ( +
  • + + `inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200 ${ + isActive && "text-gray-800 dark:text-gray-100" + }` + } + children={({ isActive }) => { + return ( + <> + {isActive && ( + + )} + {/*
  • + ))} +
+
+ ) ); }; diff --git a/frontend/src/components/widgets/button.component.tsx b/frontend/src/components/widgets/button.component.tsx index b5a6177..38a382f 100644 --- a/frontend/src/components/widgets/button.component.tsx +++ b/frontend/src/components/widgets/button.component.tsx @@ -65,12 +65,12 @@ const Button = React.forwardRef(function Button(props, ref) { return !!icon || !!iconLeft || !!iconRight; } - console.warn( - hasIcon() && !other["aria-label"] && !children, - "Button", - 'You are using an icon button, but no "aria-label" attribute was found. Add an "aria-label" attribute to work as a label for screen readers.' - ); - + if (hasIcon() && !other["aria-label"] && !children) { + console.warn( + "Button", + 'You are using an icon button, but no "aria-label" attribute was found. Add an "aria-label" attribute to work as a label for screen readers.' + ); + } const IconLeft = iconLeft || icon; const IconRight = iconRight; diff --git a/frontend/src/components/widgets/helper-text.component.tsx b/frontend/src/components/widgets/helper-text.component.tsx new file mode 100644 index 0000000..ad225af --- /dev/null +++ b/frontend/src/components/widgets/helper-text.component.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import classNames from "classnames"; +import { defaultTheme } from "../../constants"; + +export interface HelperTextProps extends React.HTMLAttributes { + /** + * Defines the color of the helper text (the same as with Input, Select, etc.) + */ + valid?: boolean; +} + +const HelperText = React.forwardRef( + function HelperText(props, ref) { + const { children, valid, className, ...other } = props; + const { helperText } = defaultTheme; + const baseStyle = helperText.base; + const validStyle = helperText.valid; + const invalidStyle = helperText.invalid; + + const validationStyle = (valid: boolean | undefined): string => { + switch (valid) { + case true: + return validStyle; + case false: + return invalidStyle; + default: + return ""; + } + }; + + const cls = classNames(baseStyle, validationStyle(valid), className); + + return ( + + {children} + + ); + } +); + +export default HelperText; diff --git a/frontend/src/components/widgets/image-fallback.component.tsx b/frontend/src/components/widgets/image-fallback.component.tsx new file mode 100644 index 0000000..32efd95 --- /dev/null +++ b/frontend/src/components/widgets/image-fallback.component.tsx @@ -0,0 +1,18 @@ +import React, { SyntheticEvent } from "react"; + +interface ImageWithFallbackProps + extends React.ImgHTMLAttributes { + src: string; + fallback: string; +} +const ImageWithFallback = ({ + fallback, + src, + ...props +}: ImageWithFallbackProps) => { + return ( + (e.target.src = fallback)} /> + ); +}; + +export default ImageWithFallback; diff --git a/frontend/src/components/widgets/index.ts b/frontend/src/components/widgets/index.ts index dd974bf..fd6ec66 100644 --- a/frontend/src/components/widgets/index.ts +++ b/frontend/src/components/widgets/index.ts @@ -2,7 +2,19 @@ import Avatar from "./avatar.component"; import Badge from "./badge.component"; import Button from "./button.component"; import { Dropdown, DropdownItem } from "./dropdown.component"; +import HelperText from "./helper-text.component"; +import ImageWithFallback from "./image-fallback.component"; import Input from "./input.component"; import Label from "./label.component"; -export { Avatar, Badge, Button, Input, Dropdown, DropdownItem, Label }; +export { + Avatar, + Badge, + Button, + Input, + Dropdown, + DropdownItem, + Label, + HelperText, + ImageWithFallback, +}; diff --git a/frontend/src/components/widgets/input.component.tsx b/frontend/src/components/widgets/input.component.tsx index 9d6aff4..3c02a9d 100644 --- a/frontend/src/components/widgets/input.component.tsx +++ b/frontend/src/components/widgets/input.component.tsx @@ -6,13 +6,23 @@ export interface InputProps extends React.ComponentPropsWithRef<"input"> { valid?: boolean; disabled?: boolean; type?: string; + formControlName?: string; + register?: any; } const Input = React.forwardRef(function Input( props, ref ) { - const { valid, disabled, className, type = "text", ...other } = props; + const { + valid, + disabled, + className, + type = "text", + formControlName, + register, + ...other + } = props; const { input } = defaultTheme; const baseStyle = input.base; diff --git a/frontend/src/containers/layout.container.tsx b/frontend/src/containers/layout.container.tsx index a09bfec..40a44db 100644 --- a/frontend/src/containers/layout.container.tsx +++ b/frontend/src/containers/layout.container.tsx @@ -2,7 +2,7 @@ import React, { Suspense } from "react"; import { useLocation, Routes, Route } from "react-router-dom"; import Header from "../components/header.component"; import Main from "./main.container"; -import { ChannelPage, PlayerPage } from "../pages"; +import { ChannelPage, HomePage, PlayerPage } from "../pages"; import ThemedSuspence from "../components/themed-suspence.component"; import { SidebarContext } from "../context"; import Sidebar from "../components/sidebar"; @@ -28,6 +28,7 @@ const Layout = () => { } /> } /> + } /> diff --git a/frontend/src/containers/main.container.tsx b/frontend/src/containers/main.container.tsx index 2e35f2d..04f386c 100644 --- a/frontend/src/containers/main.container.tsx +++ b/frontend/src/containers/main.container.tsx @@ -5,7 +5,7 @@ interface IMainProps { } const Main = ({ children }: IMainProps) => { return ( -
+
{children}
); diff --git a/frontend/src/index.css b/frontend/src/index.css index 9a1a29d..5ed3df8 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -3,3 +3,50 @@ @tailwind base; @tailwind components; @tailwind utilities; + +html { + scrollbar-width: thin; +} + +.scroller:hover::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +.scroller:hover::-webkit-scrollbar-thumb, +#aside.scroller:hover::-webkit-scrollbar-thumb { + @apply rounded; +} + +html { + @apply overflow-auto; + scrollbar-color: #9ca3af #e5e7eb; +} + +.scroller:hover::-webkit-scrollbar-track { + @apply bg-gray-200; +} + +.scroller:hover::-webkit-scrollbar-thumb { + @apply bg-gray-400; +} + +.scroller:hover::-webkit-scrollbar-thumb:hover { + @apply bg-gray-500; +} + +html.dark { + scrollbar-color: #374151 #111827; +} + +html.dark.scroller:hover::-webkit-scrollbar-track { + @apply bg-gray-900; +} + +html.dark.scroller:hover::-webkit-scrollbar-thumb { + @apply bg-gray-700; +} + +html.dark.scroller:hover::-webkit-scrollbar-thumb:hover { + @apply bg-gray-600; +} diff --git a/frontend/src/pages/channel.page.tsx b/frontend/src/pages/channel.page.tsx index 8d2fac7..6624a35 100644 --- a/frontend/src/pages/channel.page.tsx +++ b/frontend/src/pages/channel.page.tsx @@ -4,12 +4,7 @@ import { AiOutlinePlayCircle } from "react-icons/ai"; import { Stream } from "../models/stream"; import { convertEpochToSpecificTimezone } from "../utils/date-utils"; import { EPGComponent } from "../components"; -import { - CastButton, - CastProvider, - useCast, - useMedia, -} from "../utils/chromecast"; + import { toast } from "react-toastify"; import { Table, @@ -19,16 +14,13 @@ import { TableHeader, TableRow, } from "../components/widgets/table"; -import { Avatar, Badge, Button } from "../components/widgets"; +import { Badge, Button, ImageWithFallback } from "../components/widgets"; import { ApiService } from "../services"; const ChannelPage = () => { let params = useParams(); - const cast = useCast(); - const media = useMedia(); const [streams, setStreams] = React.useState([]); - const [currentVideoUrl, setCurrentVideoUrl] = React.useState(""); React.useEffect(() => { const fetchChannels = async () => { @@ -41,21 +33,6 @@ const ChannelPage = () => { fetchChannels().catch(console.error); }, [params.channelId]); - - - const _cast = React.useCallback( - async (streamId: number) => { - const streamUrl = await ApiService.getStreamUrl(streamId); - if (streamUrl) { - await media.playMedia(streamUrl); - } - }, - [media] - ); - - const handleXHR = (...args: any[]) => { - console.log("channel.page", "handleXHR", args); - }; const playStream = async (streamId: number) => { const url = await ApiService.getStreamUrl(streamId); if (url) { @@ -125,63 +102,61 @@ const ChannelPage = () => { } }; return ( - - - - - - Channel - Type - - - - - {streams.map((stream: Stream) => [ - - -
- -
-

{stream.name}

-

- Added: {convertEpochToSpecificTimezone(stream.added)} -

-
+ +
+ + + Channel + Type + + + + + {streams.map((stream: Stream) => [ + + +
+ +
+

{stream.name}

+

+ Added: {convertEpochToSpecificTimezone(stream.added)} +

- - - {stream.stream_type} - - -
- - -
-
- , -
- {false && ( - - )} - , - ])} - -
- Loading epg}> - - -
-
-
+ + + + {stream.stream_type} + + +
+ +
+
+ , + + {false && ( + + Loading epg}> + + + + )} + , + ])} + + + ); }; diff --git a/frontend/src/pages/onboarding.page.tsx b/frontend/src/pages/onboarding.page.tsx index 50ddb63..0d03144 100644 --- a/frontend/src/pages/onboarding.page.tsx +++ b/frontend/src/pages/onboarding.page.tsx @@ -1,8 +1,6 @@ -import React from "react"; import ImageLight from "../assets/images/love-tv.jpg"; -import { Button, Input, Label } from "../components/widgets"; +import ServerDetails from "../components/server-details.component"; const OnboardingPage = () => { - const _setupOnboarding = () => {}; return (
@@ -22,34 +20,7 @@ const OnboardingPage = () => { />
-
-

- Login -

- - - - - - -
-
+
diff --git a/frontend/src/services/api.service.ts b/frontend/src/services/api.service.ts index e42ef68..3fbc4ab 100644 --- a/frontend/src/services/api.service.ts +++ b/frontend/src/services/api.service.ts @@ -1,8 +1,31 @@ import http from "./http.service"; import { Channel } from "../models/channel"; import { Stream } from "../models/stream"; +import axios from "axios"; class ApiService { + public validateCredentials = async ( + server: string, + username: string, + password: string + ): Promise => { + const client = axios.create({ + baseURL: process.env.REACT_APP_API_URL, + headers: { + "Content-type": "application/json", + "x-xtream-server": server, + "x-xtream-username": username, + "x-xtream-password": password, + }, + }); + try { + const res = await client.get("/validate"); + return res.status === 200; + } catch { + return false; + } + }; + public getChannels = async (): Promise => { const response = await http.get("/channels"); return response.data; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index dbca9d6..ac64163 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -7149,6 +7149,11 @@ react-helmet@^6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" +react-hook-form@^7.29.0: + version "7.29.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.29.0.tgz#5e7e41a483b70731720966ed8be52163ea1fecf1" + integrity sha512-NcJqWRF6el5HMW30fqZRt27s+lorvlCCDbTpAyHoodQeYWXgQCvZJJQLC1kRMKdrJknVH0NIg3At6TUzlZJFOQ== + react-icons@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"