mirror of
https://github.com/fergalmoran/xtreamium.git
synced 2025-12-22 01:34:11 +00:00
Creds sending done
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
115
frontend/public/images/unknown-stream.svg
Normal file
115
frontend/public/images/unknown-stream.svg
Normal 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 |
@@ -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>
|
||||
|
||||
BIN
frontend/src/assets/images/tv.png
Normal file
BIN
frontend/src/assets/images/tv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -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,
|
||||
};
|
||||
|
||||
107
frontend/src/components/server-details.component.tsx
Normal file
107
frontend/src/components/server-details.component.tsx
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
41
frontend/src/components/widgets/helper-text.component.tsx
Normal file
41
frontend/src/components/widgets/helper-text.component.tsx
Normal 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;
|
||||
18
frontend/src/components/widgets/image-fallback.component.tsx
Normal file
18
frontend/src/components/widgets/image-fallback.component.tsx
Normal 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;
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user