Creds sending done

This commit is contained in:
Fergal Moran
2022-04-11 23:03:11 +01:00
parent 81991a22b6
commit 195b48082c
22 changed files with 559 additions and 194 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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",

View File

@@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="700pt" height="700pt" version="1.1" viewBox="0 0 700 700" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<symbol id="w" overflow="visible">
<path d="m18.766-1.125c-0.96875 0.5-1.9805 0.875-3.0312 1.125-1.043 0.25781-2.1367 0.39062-3.2812 0.39062-3.3984 0-6.0898-0.94531-8.0781-2.8438-1.9922-1.9062-2.9844-4.4844-2.9844-7.7344 0-3.2578 0.99219-5.8359 2.9844-7.7344 1.9883-1.9062 4.6797-2.8594 8.0781-2.8594 1.1445 0 2.2383 0.13281 3.2812 0.39062 1.0508 0.25 2.0625 0.625 3.0312 1.125v4.2188c-0.98047-0.65625-1.9453-1.1406-2.8906-1.4531-0.94922-0.3125-1.9492-0.46875-3-0.46875-1.875 0-3.3516 0.60547-4.4219 1.8125-1.0742 1.1992-1.6094 2.8555-1.6094 4.9688 0 2.1055 0.53516 3.7617 1.6094 4.9688 1.0703 1.1992 2.5469 1.7969 4.4219 1.7969 1.0508 0 2.0508-0.14844 3-0.45312 0.94531-0.3125 1.9102-0.80078 2.8906-1.4688z"/>
</symbol>
<symbol id="c" overflow="visible">
<path d="m13.734-11.141c-0.4375-0.19531-0.87109-0.34375-1.2969-0.4375-0.41797-0.10156-0.83984-0.15625-1.2656-0.15625-1.2617 0-2.2305 0.40625-2.9062 1.2188-0.67969 0.80469-1.0156 1.9531-1.0156 3.4531v7.0625h-4.8906v-15.312h4.8906v2.5156c0.625-1 1.3438-1.7266 2.1562-2.1875 0.82031-0.46875 1.8008-0.70312 2.9375-0.70312 0.16406 0 0.34375 0.011719 0.53125 0.03125 0.19531 0.011719 0.47656 0.039062 0.84375 0.078125z"/>
</symbol>
<symbol id="a" overflow="visible">
<path d="m17.641-7.7031v1.4062h-11.453c0.125 1.1484 0.53906 2.0078 1.25 2.5781 0.70703 0.57422 1.7031 0.85938 2.9844 0.85938 1.0312 0 2.082-0.14844 3.1562-0.45312 1.082-0.3125 2.1914-0.77344 3.3281-1.3906v3.7656c-1.1562 0.4375-2.3125 0.76562-3.4688 0.98438-1.1562 0.22656-2.3125 0.34375-3.4688 0.34375-2.7734 0-4.9297-0.70312-6.4688-2.1094-1.5312-1.4062-2.2969-3.3789-2.2969-5.9219 0-2.5 0.75391-4.4609 2.2656-5.8906 1.5078-1.4375 3.582-2.1562 6.2188-2.1562 2.4062 0 4.332 0.73047 5.7812 2.1875 1.4453 1.4492 2.1719 3.3828 2.1719 5.7969zm-5.0312-1.625c0-0.92578-0.27344-1.6719-0.8125-2.2344-0.54297-0.57031-1.25-0.85938-2.125-0.85938-0.94922 0-1.7188 0.26562-2.3125 0.79688s-0.96484 1.2969-1.1094 2.2969z"/>
</symbol>
<symbol id="k" overflow="visible">
<path d="m9.2188-6.8906c-1.0234 0-1.793 0.17188-2.3125 0.51562-0.51172 0.34375-0.76562 0.85547-0.76562 1.5312 0 0.625 0.20703 1.1172 0.625 1.4688 0.41406 0.34375 0.98828 0.51562 1.7188 0.51562 0.92578 0 1.7031-0.32812 2.3281-0.98438 0.63281-0.66406 0.95312-1.4922 0.95312-2.4844v-0.5625zm7.4688-1.8438v8.7344h-4.9219v-2.2656c-0.65625 0.92969-1.3984 1.6055-2.2188 2.0312-0.82422 0.41406-1.8242 0.625-3 0.625-1.5859 0-2.8711-0.45703-3.8594-1.375-0.99219-0.92578-1.4844-2.1289-1.4844-3.6094 0-1.7891 0.61328-3.1016 1.8438-3.9375 1.2383-0.84375 3.1797-1.2656 5.8281-1.2656h2.8906v-0.39062c0-0.76953-0.30859-1.332-0.92188-1.6875-0.61719-0.36328-1.5703-0.54688-2.8594-0.54688-1.0547 0-2.0312 0.10547-2.9375 0.3125-0.89844 0.21094-1.7305 0.52344-2.5 0.9375v-3.7344c1.0391-0.25 2.0859-0.44141 3.1406-0.57812 1.0625-0.13281 2.125-0.20312 3.1875-0.20312 2.7578 0 4.75 0.54688 5.9688 1.6406 1.2266 1.0859 1.8438 2.8555 1.8438 5.3125z"/>
</symbol>
<symbol id="b" overflow="visible">
<path d="m7.7031-19.656v4.3438h5.0469v3.5h-5.0469v6.5c0 0.71094 0.14062 1.1875 0.42188 1.4375s0.83594 0.375 1.6719 0.375h2.5156v3.5h-4.1875c-1.9375 0-3.3125-0.39844-4.125-1.2031-0.80469-0.8125-1.2031-2.1797-1.2031-4.1094v-6.5h-2.4219v-3.5h2.4219v-4.3438z"/>
</symbol>
<symbol id="j" overflow="visible">
<path d="m12.766-13.078v-8.2031h4.9219v21.281h-4.9219v-2.2188c-0.66797 0.90625-1.4062 1.5703-2.2188 1.9844s-1.7578 0.625-2.8281 0.625c-1.8867 0-3.4336-0.75-4.6406-2.25-1.2109-1.5-1.8125-3.4258-1.8125-5.7812 0-2.3633 0.60156-4.2969 1.8125-5.7969 1.207-1.5 2.7539-2.25 4.6406-2.25 1.0625 0 2 0.21484 2.8125 0.64062 0.82031 0.42969 1.5664 1.0859 2.2344 1.9688zm-3.2188 9.9219c1.0391 0 1.8359-0.37891 2.3906-1.1406 0.55078-0.76953 0.82812-1.8828 0.82812-3.3438 0-1.457-0.27734-2.5664-0.82812-3.3281-0.55469-0.76953-1.3516-1.1562-2.3906-1.1562-1.043 0-1.8398 0.38672-2.3906 1.1562-0.55469 0.76172-0.82812 1.8711-0.82812 3.3281 0 1.4609 0.27344 2.5742 0.82812 3.3438 0.55078 0.76172 1.3477 1.1406 2.3906 1.1406z"/>
</symbol>
<symbol id="i" overflow="visible">
<path d="m10.5-3.1562c1.0508 0 1.8516-0.37891 2.4062-1.1406 0.55078-0.76953 0.82812-1.8828 0.82812-3.3438 0-1.457-0.27734-2.5664-0.82812-3.3281-0.55469-0.76953-1.3555-1.1562-2.4062-1.1562-1.0547 0-1.8594 0.38672-2.4219 1.1562-0.55469 0.77344-0.82812 1.8828-0.82812 3.3281 0 1.4492 0.27344 2.5586 0.82812 3.3281 0.5625 0.77344 1.3672 1.1562 2.4219 1.1562zm-3.25-9.9219c0.67578-0.88281 1.4219-1.5391 2.2344-1.9688 0.82031-0.42578 1.7656-0.64062 2.8281-0.64062 1.8945 0 3.4453 0.75 4.6562 2.25 1.207 1.5 1.8125 3.4336 1.8125 5.7969 0 2.3555-0.60547 4.2812-1.8125 5.7812-1.2109 1.5-2.7617 2.25-4.6562 2.25-1.0625 0-2.0078-0.21094-2.8281-0.625-0.8125-0.42578-1.5586-1.0859-2.2344-1.9844v2.2188h-4.8906v-21.281h4.8906z"/>
</symbol>
<symbol id="h" overflow="visible">
<path d="m0.34375-15.312h4.8906l4.125 10.391 3.5-10.391h4.8906l-6.4375 16.766c-0.64844 1.6953-1.4023 2.8828-2.2656 3.5625-0.86719 0.6875-2 1.0312-3.4062 1.0312h-2.8438v-3.2188h1.5312c0.83203 0 1.4375-0.13672 1.8125-0.40625 0.38281-0.26172 0.67969-0.73047 0.89062-1.4062l0.14062-0.42188z"/>
</symbol>
<symbol id="e" overflow="visible">
<path d="m2.5781-20.406h5.875l7.4219 14v-14h4.9844v20.406h-5.875l-7.4219-14v14h-4.9844z"/>
</symbol>
<symbol id="g" overflow="visible">
<path d="m2.3594-15.312h4.8906v15.312h-4.8906zm0-5.9688h4.8906v4h-4.8906z"/>
</symbol>
<symbol id="v" overflow="visible">
<path d="m2.3594-21.281h4.8906v11.594l5.625-5.625h5.6875l-7.4688 7.0312 8.0625 8.2812h-5.9375l-5.9688-6.3906v6.3906h-4.8906z"/>
</symbol>
<symbol id="f" overflow="visible">
<path d="m17.75-9.3281v9.3281h-4.9219v-7.1094c0-1.3438-0.03125-2.2656-0.09375-2.7656s-0.16797-0.86719-0.3125-1.1094c-0.1875-0.3125-0.44922-0.55469-0.78125-0.73438-0.32422-0.17578-0.69531-0.26562-1.1094-0.26562-1.0234 0-1.8242 0.39844-2.4062 1.1875-0.58594 0.78125-0.875 1.8711-0.875 3.2656v7.5312h-4.8906v-21.281h4.8906v8.2031c0.73828-0.88281 1.5195-1.5391 2.3438-1.9688 0.83203-0.42578 1.75-0.64062 2.75-0.64062 1.7695 0 3.1133 0.54688 4.0312 1.6406 0.91406 1.0859 1.375 2.6562 1.375 4.7188z"/>
</symbol>
<symbol id="u" overflow="visible">
<path d="m2.3594-21.281h4.8906v21.281h-4.8906z"/>
</symbol>
<symbol id="t" overflow="visible">
<path d="m7.8281-16.438v12.453h1.8906c2.1562 0 3.8008-0.53125 4.9375-1.5938 1.1328-1.0625 1.7031-2.6133 1.7031-4.6562 0-2.0195-0.57031-3.5547-1.7031-4.6094-1.125-1.0625-2.7734-1.5938-4.9375-1.5938zm-5.25-3.9688h5.5469c3.0938 0 5.3984 0.21875 6.9219 0.65625 1.5195 0.4375 2.8203 1.1875 3.9062 2.25 0.95703 0.91797 1.6641 1.9805 2.125 3.1875 0.46875 1.1992 0.70312 2.5586 0.70312 4.0781 0 1.543-0.23438 2.918-0.70312 4.125-0.46094 1.2109-1.168 2.2773-2.125 3.2031-1.0938 1.0547-2.4062 1.8047-3.9375 2.25-1.5312 0.4375-3.8281 0.65625-6.8906 0.65625h-5.5469z"/>
</symbol>
<symbol id="s" overflow="visible">
<path d="m0.42188-15.312h4.8906l3.8281 10.578 3.7969-10.578h4.9062l-6.0312 15.312h-5.375z"/>
</symbol>
<symbol id="r" overflow="visible">
<path d="m12.422-21.281v3.2188h-2.7031c-0.6875 0-1.1719 0.125-1.4531 0.375-0.27344 0.25-0.40625 0.6875-0.40625 1.3125v1.0625h4.1875v3.5h-4.1875v11.812h-4.8906v-11.812h-2.4375v-3.5h2.4375v-1.0625c0-1.6641 0.46094-2.8984 1.3906-3.7031 0.92578-0.80078 2.3672-1.2031 4.3281-1.2031z"/>
</symbol>
<symbol id="d" overflow="visible">
<path d="m9.6406-12.188c-1.0859 0-1.9141 0.39062-2.4844 1.1719-0.57422 0.78125-0.85938 1.9062-0.85938 3.375s0.28516 2.5938 0.85938 3.375c0.57031 0.77344 1.3984 1.1562 2.4844 1.1562 1.0625 0 1.875-0.38281 2.4375-1.1562 0.57031-0.78125 0.85938-1.9062 0.85938-3.375s-0.28906-2.5938-0.85938-3.375c-0.5625-0.78125-1.375-1.1719-2.4375-1.1719zm0-3.5c2.6328 0 4.6914 0.71484 6.1719 2.1406 1.4766 1.418 2.2188 3.3867 2.2188 5.9062 0 2.5117-0.74219 4.4805-2.2188 5.9062-1.4805 1.418-3.5391 2.125-6.1719 2.125-2.6484 0-4.7148-0.70703-6.2031-2.125-1.4922-1.4258-2.2344-3.3945-2.2344-5.9062 0-2.5195 0.74219-4.4883 2.2344-5.9062 1.4883-1.4258 3.5547-2.1406 6.2031-2.1406z"/>
</symbol>
<symbol id="q" overflow="visible">
<path d="m16.547-12.766c0.61328-0.94531 1.3477-1.6719 2.2031-2.1719 0.85156-0.5 1.7891-0.75 2.8125-0.75 1.7578 0 3.0977 0.54688 4.0156 1.6406 0.92578 1.0859 1.3906 2.6562 1.3906 4.7188v9.3281h-4.9219v-7.9844-0.35938c0.007813-0.13281 0.015625-0.32031 0.015625-0.5625 0-1.082-0.16406-1.8633-0.48438-2.3438-0.3125-0.48828-0.82422-0.73438-1.5312-0.73438-0.92969 0-1.6484 0.38672-2.1562 1.1562-0.51172 0.76172-0.77344 1.8672-0.78125 3.3125v7.5156h-4.9219v-7.9844c0-1.6953-0.14844-2.7852-0.4375-3.2656-0.29297-0.48828-0.8125-0.73438-1.5625-0.73438-0.9375 0-1.6641 0.38672-2.1719 1.1562-0.51172 0.76172-0.76562 1.8594-0.76562 3.2969v7.5312h-4.9219v-15.312h4.9219v2.2344c0.60156-0.86328 1.2891-1.5156 2.0625-1.9531 0.78125-0.4375 1.6406-0.65625 2.5781-0.65625 1.0625 0 2 0.25781 2.8125 0.76562 0.8125 0.51172 1.4258 1.2305 1.8438 2.1562z"/>
</symbol>
<symbol id="p" overflow="visible">
<path d="m2.1875-5.9688v-9.3438h4.9219v1.5312c0 0.83594-0.007813 1.875-0.015625 3.125-0.011719 1.25-0.015625 2.0859-0.015625 2.5 0 1.2422 0.03125 2.1328 0.09375 2.6719 0.070313 0.54297 0.17969 0.93359 0.32812 1.1719 0.20703 0.32422 0.47266 0.57422 0.79688 0.75 0.32031 0.16797 0.69141 0.25 1.1094 0.25 1.0195 0 1.8203-0.39062 2.4062-1.1719 0.58203-0.78125 0.875-1.8672 0.875-3.2656v-7.5625h4.8906v15.312h-4.8906v-2.2188c-0.74219 0.89844-1.5234 1.5586-2.3438 1.9844-0.82422 0.41406-1.7344 0.625-2.7344 0.625-1.7617 0-3.1055-0.53906-4.0312-1.625-0.92969-1.082-1.3906-2.6602-1.3906-4.7344z"/>
</symbol>
<symbol id="o" overflow="visible">
<path d="m17.75-9.3281v9.3281h-4.9219v-7.1406c0-1.3203-0.03125-2.2344-0.09375-2.7344s-0.16797-0.86719-0.3125-1.1094c-0.1875-0.3125-0.44922-0.55469-0.78125-0.73438-0.32422-0.17578-0.69531-0.26562-1.1094-0.26562-1.0234 0-1.8242 0.39844-2.4062 1.1875-0.58594 0.78125-0.875 1.8711-0.875 3.2656v7.5312h-4.8906v-15.312h4.8906v2.2344c0.73828-0.88281 1.5195-1.5391 2.3438-1.9688 0.83203-0.42578 1.75-0.64062 2.75-0.64062 1.7695 0 3.1133 0.54688 4.0312 1.6406 0.91406 1.0859 1.375 2.6562 1.375 4.7188z"/>
</symbol>
<symbol id="n" overflow="visible">
<path d="m2.5781-20.406h8.7344c2.5938 0 4.582 0.57812 5.9688 1.7344 1.3945 1.1484 2.0938 2.7891 2.0938 4.9219 0 2.1367-0.69922 3.7812-2.0938 4.9375-1.3867 1.1562-3.375 1.7344-5.9688 1.7344h-3.4844v7.0781h-5.25zm5.25 3.8125v5.7031h2.9219c1.0195 0 1.8047-0.25 2.3594-0.75 0.5625-0.5 0.84375-1.2031 0.84375-2.1094 0-0.91406-0.28125-1.6172-0.84375-2.1094-0.55469-0.48828-1.3398-0.73438-2.3594-0.73438z"/>
</symbol>
<symbol id="m" overflow="visible">
<path d="m2.3594-15.312h4.8906v15.031c0 2.0508-0.49609 3.6172-1.4844 4.7031-0.98047 1.082-2.4062 1.625-4.2812 1.625h-2.4219v-3.2188h0.85938c0.92578 0 1.5625-0.21094 1.9062-0.625 0.35156-0.41797 0.53125-1.2461 0.53125-2.4844zm0-5.9688h4.8906v4h-4.8906z"/>
</symbol>
<symbol id="l" overflow="visible">
<path d="m14.719-14.828v3.9844c-0.65625-0.45703-1.3242-0.79688-2-1.0156-0.66797-0.21875-1.3594-0.32812-2.0781-0.32812-1.3672 0-2.4336 0.40234-3.2031 1.2031-0.76172 0.79297-1.1406 1.9062-1.1406 3.3438 0 1.4297 0.37891 2.543 1.1406 3.3438 0.76953 0.79297 1.8359 1.1875 3.2031 1.1875 0.75781 0 1.4844-0.10938 2.1719-0.32812 0.6875-0.22656 1.3203-0.56641 1.9062-1.0156v4c-0.76172 0.28125-1.5391 0.48828-2.3281 0.625-0.78125 0.14453-1.5742 0.21875-2.375 0.21875-2.7617 0-4.9219-0.70703-6.4844-2.125-1.5547-1.4141-2.3281-3.3828-2.3281-5.9062 0-2.5312 0.77344-4.5039 2.3281-5.9219 1.5625-1.4141 3.7227-2.125 6.4844-2.125 0.80078 0 1.5938 0.074219 2.375 0.21875 0.78125 0.13672 1.5547 0.35156 2.3281 0.64062z"/>
</symbol>
</defs>
<g>
<path d="m-1005.2 0h1036v554.4h-1036z"/>
<path d="m588.68 103.72h-477.37c-18.531 0-33.59 18.039-33.59 40.27v272.02c0 22.266 15.059 40.27 33.59 40.27h477.37c18.535 0 33.594-18.008 33.594-40.27v-272.01c0-22.238-15.059-40.273-33.594-40.273zm-104.38 278.76c0 16.762-11.328 30.32-25.277 30.32l-311.24-0.003906c-13.945 0-25.277-13.559-25.277-30.32v-204.93c0-16.762 11.328-30.352 25.277-30.352h311.24c13.945 0 25.277 13.59 25.277 30.352v204.93zm69.148-51.992c-23.375 0-42.363-18.996-42.363-42.438 0-23.375 18.996-42.363 42.363-42.363 23.445 0 42.363 18.996 42.363 42.363 0 23.445-18.914 42.438-42.363 42.438z" fill-rule="evenodd"/>
<path d="m241.23 188.95 157.68 91.055-157.68 91.086z"/>
<use x="70" y="644" xlink:href="#w"/>
<use x="90.550781" y="644" xlink:href="#c"/>
<use x="104.359375" y="644" xlink:href="#a"/>
<use x="123.347656" y="644" xlink:href="#k"/>
<use x="142.242188" y="644" xlink:href="#b"/>
<use x="155.628906" y="644" xlink:href="#a"/>
<use x="174.617188" y="644" xlink:href="#j"/>
<use x="204.410156" y="644" xlink:href="#i"/>
<use x="224.453125" y="644" xlink:href="#h"/>
<use x="252.453125" y="644" xlink:href="#e"/>
<use x="275.886719" y="644" xlink:href="#g"/>
<use x="285.484375" y="644" xlink:href="#v"/>
<use x="304.105469" y="644" xlink:href="#f"/>
<use x="324.039062" y="644" xlink:href="#g"/>
<use x="333.632812" y="644" xlink:href="#u"/>
<use x="352.980469" y="644" xlink:href="#t"/>
<use x="376.222656" y="644" xlink:href="#a"/>
<use x="395.210938" y="644" xlink:href="#s"/>
<use x="70" y="672" xlink:href="#r"/>
<use x="82.183594" y="672" xlink:href="#c"/>
<use x="95.992188" y="672" xlink:href="#d"/>
<use x="115.226562" y="672" xlink:href="#q"/>
<use x="154.152344" y="672" xlink:href="#b"/>
<use x="167.535156" y="672" xlink:href="#f"/>
<use x="187.46875" y="672" xlink:href="#a"/>
<use x="216.207031" y="672" xlink:href="#e"/>
<use x="239.640625" y="672" xlink:href="#d"/>
<use x="258.878906" y="672" xlink:href="#p"/>
<use x="278.8125" y="672" xlink:href="#o"/>
<use x="308.492188" y="672" xlink:href="#n"/>
<use x="329.015625" y="672" xlink:href="#c"/>
<use x="342.820312" y="672" xlink:href="#d"/>
<use x="362.058594" y="672" xlink:href="#m"/>
<use x="371.65625" y="672" xlink:href="#a"/>
<use x="390.648438" y="672" xlink:href="#l"/>
<use x="407.242188" y="672" xlink:href="#b"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -8,9 +8,12 @@ function App() {
<BrowserRouter>
<Routes>
{localStorage.getItem("server") ? (
<Route path="/*" element={<OnboardingPage />} />
<>
<Route path="/onboarding" element={<OnboardingPage />} />
<Route path="/*" element={<Layout />} />
</>
) : (
<Route path="/*" element={<Layout />} />
<Route path="/*" element={<OnboardingPage />} />
)}
</Routes>
</BrowserRouter>

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -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,
};

View File

@@ -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 (
<div className="w-full">
<h1 className="mb-4 text-xl font-semibold text-gray-700 dark:text-gray-200">
XTream Codes Details
</h1>
<form onSubmit={handleSubmit(onSubmit)} autoComplete="false">
<input
autoComplete="false"
name="hidden"
type="text"
style={{ display: "none" }}
/>
<Label>
<span>Server address</span>
<Input
className="mt-1"
type="text"
placeholder="my.streams.com"
autoComplete="off"
{...register("server", {
required: "Server is required",
})}
valid={!errors.server}
/>
{errors.server && (
<HelperText valid={false}>{errors.server.message}</HelperText>
)}
</Label>
<Label className="mt-4">
<span>Username</span>
<Input
className="mt-1"
type="text"
placeholder="username"
autoComplete="off"
data-lpignore="true"
{...register("username", {
required: "Username is required",
})}
valid={!errors.username}
/>
{errors.username && (
<HelperText valid={false}>{errors.username.message}</HelperText>
)}{" "}
</Label>
<Label className="mt-4">
<span>Password</span>
<Input
className="mt-1"
type="password"
autoComplete="off"
data-lpignore="true"
placeholder="***************"
{...register("password", {
required: "Password is required",
})}
valid={!errors.password}
/>
{errors.password && (
<HelperText valid={false}>{errors.password.message}</HelperText>
)}
</Label>
<Button
className="mt-4"
block
type="submit"
aria-label="Submit"
icon={BiRocket}
>
Let's go!
</Button>
<hr className="my-8" />
</form>
</div>
);
};
export default ServerDetails;

View File

@@ -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 ? (
<aside className="fixed inset-y-0 z-50 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 lg:hidden">
<SidebarContent />
</aside>
) : null;
return (
<Transition
show={isSidebarOpen}
enter="transition-opacity duration-75"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<aside className="fixed inset-y-0 z-50 flex-shrink-0 w-64 mt-16 overflow-y-auto bg-white dark:bg-gray-800 lg:hidden">
<SidebarContent />
</aside>
</Transition>
);
};
export default MobileSidebar;

View File

@@ -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<Channel[]>([]);
const [filteredChannels, setFilteredChannels] = React.useState<Channel[]>([]);
// const [filteredChannels, setFilteredChannels] = React.useState<Channel[]>([]);
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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
// 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 (
<div className="py-4 text-gray-500 dark:text-gray-400">
<a
className="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200"
href="/"
>
Xtreamium
</a>
<ul className="mt-6">
{filteredChannels.map((channel: Channel) => (
<li className="relative px-6 py-3" key={channel.category_id}>
<NavLink
to={`/live/channel/${channel.category_id}`}
className={({ isActive }) =>
`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 && (
<span
className="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
aria-hidden="true"
></span>
)}
{/* <Icon className="w-5 h-5" aria-hidden="true" icon={route.icon} /> */}
<span className="ml-4">{channel.category_name}</span>
</>
);
}}
/>
</li>
))}
</ul>
</div>
channels && (
<div className="py-4 text-gray-500 dark:text-gray-400 scroller">
<a
className="ml-6 text-lg font-bold text-gray-800 dark:text-gray-200"
href="/"
>
Xtreamium
</a>
<ul className="mt-6">
{channels.map((channel: Channel) => (
<li className="relative px-6 py-3" key={channel.category_id}>
<NavLink
to={`/live/channel/${channel.category_id}`}
className={({ isActive }) =>
`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 && (
<span
className="absolute inset-y-0 left-0 w-1 bg-purple-600 rounded-tr-lg rounded-br-lg"
aria-hidden="true"
></span>
)}
{/* <Icon className="w-5 h-5" aria-hidden="true" icon={route.icon} /> */}
<span className="ml-4">{channel.category_name}</span>
</>
);
}}
/>
</li>
))}
</ul>
</div>
)
);
};

View File

@@ -65,12 +65,12 @@ const Button = React.forwardRef<Ref, ButtonProps>(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;

View File

@@ -0,0 +1,41 @@
import React from "react";
import classNames from "classnames";
import { defaultTheme } from "../../constants";
export interface HelperTextProps extends React.HTMLAttributes<HTMLSpanElement> {
/**
* Defines the color of the helper text (the same as with Input, Select, etc.)
*/
valid?: boolean;
}
const HelperText = React.forwardRef<HTMLSpanElement, HelperTextProps>(
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 (
<span className={cls} ref={ref} {...other}>
{children}
</span>
);
}
);
export default HelperText;

View File

@@ -0,0 +1,18 @@
import React, { SyntheticEvent } from "react";
interface ImageWithFallbackProps
extends React.ImgHTMLAttributes<HTMLImageElement> {
src: string;
fallback: string;
}
const ImageWithFallback = ({
fallback,
src,
...props
}: ImageWithFallbackProps) => {
return (
<img {...props} src={src} onError={(e: any) => (e.target.src = fallback)} />
);
};
export default ImageWithFallback;

View File

@@ -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,
};

View File

@@ -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<HTMLInputElement, InputProps>(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;

View File

@@ -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 = () => {
<Routes>
<Route path="live/channel/:channelId" element={<ChannelPage />} />
<Route path="live/play/:streamId" element={<PlayerPage />} />
<Route path="*" element={<HomePage />} />
</Routes>
</Suspense>
</Main>

View File

@@ -5,7 +5,7 @@ interface IMainProps {
}
const Main = ({ children }: IMainProps) => {
return (
<main className="h-full overflow-y-auto">
<main className="h-screen pb-16 overflow-y-hidden">
<div className="container grid px-6 mx-auto">{children}</div>
</main>
);

View File

@@ -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;
}

View File

@@ -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<Stream[]>([]);
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 (
<CastProvider>
<TableContainer className="mt-5 mb-8">
<Table>
<TableHeader>
<TableRow>
<TableCell>Channel</TableCell>
<TableCell>Type</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHeader>
<TableBody>
{streams.map((stream: Stream) => [
<TableRow key={stream.num}>
<TableCell>
<div className="flex items-center text-sm">
<Avatar
className="hidden w-10 h-10 ml-2 mr-3 md:block"
src={stream.stream_icon}
alt="Stream icon"
/>
<div>
<p className="font-semibold">{stream.name}</p>
<p className="text-xs text-gray-600 dark:text-gray-400">
Added: {convertEpochToSpecificTimezone(stream.added)}
</p>
</div>
<TableContainer className="mt-5 mb-8">
<Table>
<TableHeader>
<TableRow>
<TableCell>Channel</TableCell>
<TableCell>Type</TableCell>
<TableCell></TableCell>
</TableRow>
</TableHeader>
<TableBody>
{streams.map((stream: Stream) => [
<TableRow key={stream.num}>
<TableCell>
<div className="flex items-center text-sm">
<ImageWithFallback
className="hidden w-10 h-10 ml-2 mr-3 md:block"
src={stream.stream_icon}
alt="Stream icon"
fallback="/images/unknown-stream.svg"
/>
<div>
<p className="font-semibold">{stream.name}</p>
<p className="text-xs text-gray-600 dark:text-gray-400">
Added: {convertEpochToSpecificTimezone(stream.added)}
</p>
</div>
</TableCell>
<TableCell>
<Badge type={`primary`}>{stream.stream_type}</Badge>
</TableCell>
<TableCell>
<div className="flex items-center space-x-4">
<Button
icon={AiOutlinePlayCircle}
layout="link"
aria-label="Edit"
onClick={() => playStream(stream.stream_id)}
></Button>
<CastButton streamId={stream.stream_id} onPlay={_cast} />
</div>
</TableCell>
</TableRow>,
<tr key={`${stream.num}-epg`}>
{false && (
<td colSpan={3} className="px-4 py-2 mt-8 border-4 shadow-md">
<Suspense fallback={<h1>Loading epg</h1>}>
<EPGComponent channelId={stream.epg_channel_id} />
</Suspense>
</td>
)}
</tr>,
])}
</TableBody>
</Table>
</TableContainer>
</CastProvider>
</div>
</TableCell>
<TableCell>
<Badge type={`primary`}>{stream.stream_type}</Badge>
</TableCell>
<TableCell>
<div className="flex items-center space-x-4">
<Button
icon={AiOutlinePlayCircle}
layout="link"
aria-label="Edit"
onClick={() => playStream(stream.stream_id)}
></Button>
</div>
</TableCell>
</TableRow>,
<tr key={`${stream.num}-epg`}>
{false && (
<td colSpan={3} className="px-4 py-2 mt-8 border-4 shadow-md">
<Suspense fallback={<h1>Loading epg</h1>}>
<EPGComponent channelId={stream.epg_channel_id} />
</Suspense>
</td>
)}
</tr>,
])}
</TableBody>
</Table>
</TableContainer>
);
};

View File

@@ -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 (
<div className="flex items-center min-h-screen p-6 bg-gray-50 dark:bg-gray-900">
<div className="flex-1 h-full max-w-4xl mx-auto overflow-hidden bg-white rounded-lg shadow-xl dark:bg-gray-800">
@@ -22,34 +20,7 @@ const OnboardingPage = () => {
/>
</div>
<main className="flex items-center justify-center p-6 sm:p-12 md:w-1/2">
<div className="w-full">
<h1 className="mb-4 text-xl font-semibold text-gray-700 dark:text-gray-200">
Login
</h1>
<Label>
<span>Email</span>
<Input
className="mt-1"
type="email"
placeholder="john@doe.com"
/>
</Label>
<Label className="mt-4">
<span>Password</span>
<Input
className="mt-1"
type="password"
placeholder="***************"
/>
</Label>
<Button className="mt-4" block onClick={_setupOnboarding}>
Log in
</Button>
<hr className="my-8" />
</div>
<ServerDetails />
</main>
</div>
</div>

View File

@@ -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<Boolean> => {
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<Channel[]> => {
const response = await http.get("/channels");
return response.data;

View File

@@ -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"