mirror of
https://github.com/fastogt/fastocloud_admin.git
synced 2025-03-09 23:38:52 +00:00
Subscribers (#4)
* Subscribers start impl * Send subscribers to service * Add subscribers * Review * User agent
This commit is contained in:
parent
197173dd66
commit
df32bb51a7
22 changed files with 388 additions and 28 deletions
|
@ -1,3 +1,8 @@
|
||||||
|
1.2.0 /
|
||||||
|
[Alexandr Topilski]
|
||||||
|
- User agents
|
||||||
|
- Subscribers
|
||||||
|
|
||||||
1.1.0 / June 6, 2019
|
1.1.0 / June 6, 2019
|
||||||
[Alexandr Topilski]
|
[Alexandr Topilski]
|
||||||
- User type
|
- User type
|
||||||
|
|
|
@ -44,6 +44,7 @@ class Client:
|
||||||
VODS_IN_DIRECTORY = 'vods_in_directory'
|
VODS_IN_DIRECTORY = 'vods_in_directory'
|
||||||
VODS_DIRECTORY = 'vods_directory'
|
VODS_DIRECTORY = 'vods_directory'
|
||||||
STREAMS = 'streams'
|
STREAMS = 'streams'
|
||||||
|
SUBSCRIBERS = 'subscribers'
|
||||||
STREAM_ID = 'id'
|
STREAM_ID = 'id'
|
||||||
LICENSE_KEY = 'license_key'
|
LICENSE_KEY = 'license_key'
|
||||||
PATH = 'path'
|
PATH = 'path'
|
||||||
|
@ -126,8 +127,8 @@ class Client:
|
||||||
self._send_request(command_id, Commands.PREPARE_SERVICE_COMMAND, command_args)
|
self._send_request(command_id, Commands.PREPARE_SERVICE_COMMAND, command_args)
|
||||||
|
|
||||||
@is_active_decorator
|
@is_active_decorator
|
||||||
def sync_service(self, command_id: int, streams: list):
|
def sync_service(self, command_id: int, streams: list, subscribers: list):
|
||||||
command_args = {Client.STREAMS: streams}
|
command_args = {Client.STREAMS: streams, Client.SUBSCRIBERS: subscribers}
|
||||||
self._send_request(command_id, Commands.SYNC_SERVICE_COMMAND, command_args)
|
self._send_request(command_id, Commands.SYNC_SERVICE_COMMAND, command_args)
|
||||||
|
|
||||||
@is_active_decorator
|
@is_active_decorator
|
||||||
|
|
|
@ -19,7 +19,7 @@ class Url(EmbeddedDocument):
|
||||||
|
|
||||||
|
|
||||||
class InputUrl(Url):
|
class InputUrl(Url):
|
||||||
pass
|
user_agent = IntField(default=constants.UserAgent.GSTREAMER, required=True)
|
||||||
|
|
||||||
|
|
||||||
class OutputUrl(Url):
|
class OutputUrl(Url):
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from wtforms import Form
|
from wtforms import Form
|
||||||
from flask_babel import lazy_gettext
|
from flask_babel import lazy_gettext
|
||||||
from wtforms.fields import StringField, FieldList, IntegerField, FormField, FloatField
|
from wtforms.fields import StringField, FieldList, IntegerField, FormField, FloatField, SelectField
|
||||||
from wtforms.validators import InputRequired, Length, NumberRange
|
from wtforms.validators import InputRequired, Length, NumberRange
|
||||||
|
|
||||||
import app.constants as constants
|
import app.constants as constants
|
||||||
|
@ -16,7 +16,9 @@ class UrlForm(Form):
|
||||||
|
|
||||||
|
|
||||||
class InputUrlForm(UrlForm):
|
class InputUrlForm(UrlForm):
|
||||||
pass
|
user_agent = SelectField(lazy_gettext(u'User agent:'),
|
||||||
|
validators=[InputRequired()],
|
||||||
|
choices=constants.AVAILABLE_USER_AGENTS, coerce=constants.UserAgent.coerce)
|
||||||
|
|
||||||
|
|
||||||
class InputUrlsForm(Form):
|
class InputUrlsForm(Form):
|
||||||
|
@ -25,7 +27,7 @@ class InputUrlsForm(Form):
|
||||||
def get_data(self) -> InputUrls:
|
def get_data(self) -> InputUrls:
|
||||||
urls = InputUrls()
|
urls = InputUrls()
|
||||||
for url in self.data['urls']:
|
for url in self.data['urls']:
|
||||||
urls.urls.append(InputUrl(url['id'], url['uri']))
|
urls.urls.append(InputUrl(url['id'], url['uri'], url['user_agent']))
|
||||||
|
|
||||||
return urls
|
return urls
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,4 @@ PUBLIC_CONFIG = {'site': {'title': 'FastoCloud', 'keywords': 'video,cloud,iptv,s
|
||||||
'support': {'contact_email': 'support@fastogt.com',
|
'support': {'contact_email': 'support@fastogt.com',
|
||||||
'contact_address': 'Republic of Belarus, Minsk, Stadionnay str. 5',
|
'contact_address': 'Republic of Belarus, Minsk, Stadionnay str. 5',
|
||||||
'community_channel': 'https://discord.gg/cnUXsws'},
|
'community_channel': 'https://discord.gg/cnUXsws'},
|
||||||
'project': {'version': '1.1.0', 'version_type': 'release'}}
|
'project': {'version': '1.2.0', 'version_type': 'release'}}
|
||||||
|
|
|
@ -195,3 +195,22 @@ class Roles(IntEnum):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.value)
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
class UserAgent(IntEnum):
|
||||||
|
GSTREAMER = 0
|
||||||
|
VLC = 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def choices(cls):
|
||||||
|
return [(choice, choice.name) for choice in cls]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def coerce(cls, item):
|
||||||
|
return cls(int(item)) if not isinstance(item, cls) else item
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.value)
|
||||||
|
|
||||||
|
|
||||||
|
AVAILABLE_USER_AGENTS = [(UserAgent.GSTREAMER, 'GStreamer'), (UserAgent.VLC, 'VLC'), ]
|
||||||
|
|
|
@ -13,6 +13,8 @@ class ServiceSettingsForm(FlaskForm):
|
||||||
host = FormField(HostAndPortForm, lazy_gettext(u'Host:'), validators=[])
|
host = FormField(HostAndPortForm, lazy_gettext(u'Host:'), validators=[])
|
||||||
http_host = FormField(HostAndPortForm, lazy_gettext(u'Http host:'), validators=[])
|
http_host = FormField(HostAndPortForm, lazy_gettext(u'Http host:'), validators=[])
|
||||||
vods_host = FormField(HostAndPortForm, lazy_gettext(u'Vods host:'), validators=[])
|
vods_host = FormField(HostAndPortForm, lazy_gettext(u'Vods host:'), validators=[])
|
||||||
|
subscribers_host = FormField(HostAndPortForm, lazy_gettext(u'Subscribers host:'), validators=[])
|
||||||
|
bandwidth_host = FormField(HostAndPortForm, lazy_gettext(u'Bandwidth host:'), validators=[])
|
||||||
|
|
||||||
feedback_directory = StringField(lazy_gettext(u'Feedback directory:'), validators=[InputRequired()])
|
feedback_directory = StringField(lazy_gettext(u'Feedback directory:'), validators=[InputRequired()])
|
||||||
timeshifts_directory = StringField(lazy_gettext(u'Timeshifts directory:'), validators=[InputRequired()])
|
timeshifts_directory = StringField(lazy_gettext(u'Timeshifts directory:'), validators=[InputRequired()])
|
||||||
|
@ -32,6 +34,8 @@ class ServiceSettingsForm(FlaskForm):
|
||||||
settings.host = self.host.get_data()
|
settings.host = self.host.get_data()
|
||||||
settings.http_host = self.http_host.get_data()
|
settings.http_host = self.http_host.get_data()
|
||||||
settings.vods_host = self.vods_host.get_data()
|
settings.vods_host = self.vods_host.get_data()
|
||||||
|
settings.subscribers_host = self.subscribers_host.get_data()
|
||||||
|
settings.bandwidth_host = self.bandwidth_host.get_data()
|
||||||
|
|
||||||
settings.feedback_directory = self.feedback_directory.data
|
settings.feedback_directory = self.feedback_directory.data
|
||||||
settings.timeshifts_directory = self.timeshifts_directory.data
|
settings.timeshifts_directory = self.timeshifts_directory.data
|
||||||
|
|
|
@ -24,6 +24,10 @@ class ServerSettings:
|
||||||
DEFAULT_SERVICE_HTTP_PORT = 8000
|
DEFAULT_SERVICE_HTTP_PORT = 8000
|
||||||
DEFAULT_SERVICE_VODS_HOST = 'localhost'
|
DEFAULT_SERVICE_VODS_HOST = 'localhost'
|
||||||
DEFAULT_SERVICE_VODS_PORT = 7000
|
DEFAULT_SERVICE_VODS_PORT = 7000
|
||||||
|
DEFAULT_SERVICE_SUBSCRIBERS_HOST = 'localhost'
|
||||||
|
DEFAULT_SERVICE_SUBSCRIBERS_PORT = 6000
|
||||||
|
DEFAULT_SERVICE_BANDWIDTH_HOST = 'localhost'
|
||||||
|
DEFAULT_SERVICE_BANDWIDTH_PORT = 5000
|
||||||
|
|
||||||
name = StringField(unique=True, default=DEFAULT_SERVICE_NAME, max_length=MAX_SERVICE_NAME_LENGTH,
|
name = StringField(unique=True, default=DEFAULT_SERVICE_NAME, max_length=MAX_SERVICE_NAME_LENGTH,
|
||||||
min_length=MIN_SERVICE_NAME_LENGTH)
|
min_length=MIN_SERVICE_NAME_LENGTH)
|
||||||
|
@ -32,6 +36,10 @@ class ServerSettings:
|
||||||
port=DEFAULT_SERVICE_HTTP_PORT))
|
port=DEFAULT_SERVICE_HTTP_PORT))
|
||||||
vods_host = EmbeddedDocumentField(HostAndPort, default=HostAndPort(host=DEFAULT_SERVICE_VODS_HOST,
|
vods_host = EmbeddedDocumentField(HostAndPort, default=HostAndPort(host=DEFAULT_SERVICE_VODS_HOST,
|
||||||
port=DEFAULT_SERVICE_VODS_PORT))
|
port=DEFAULT_SERVICE_VODS_PORT))
|
||||||
|
subscribers_host = EmbeddedDocumentField(HostAndPort, default=HostAndPort(host=DEFAULT_SERVICE_SUBSCRIBERS_HOST,
|
||||||
|
port=DEFAULT_SERVICE_SUBSCRIBERS_PORT))
|
||||||
|
bandwidth_host = EmbeddedDocumentField(HostAndPort, default=HostAndPort(host=DEFAULT_SERVICE_BANDWIDTH_HOST,
|
||||||
|
port=DEFAULT_SERVICE_BANDWIDTH_PORT))
|
||||||
|
|
||||||
feedback_directory = StringField(default=DEFAULT_FEEDBACK_DIR_PATH)
|
feedback_directory = StringField(default=DEFAULT_FEEDBACK_DIR_PATH)
|
||||||
timeshifts_directory = StringField(default=DEFAULT_TIMESHIFTS_DIR_PATH)
|
timeshifts_directory = StringField(default=DEFAULT_TIMESHIFTS_DIR_PATH)
|
||||||
|
|
|
@ -54,7 +54,7 @@ class Service(IStreamHandler):
|
||||||
self._settings = settings
|
self._settings = settings
|
||||||
self.__reload_from_db()
|
self.__reload_from_db()
|
||||||
# other fields
|
# other fields
|
||||||
self._client = ServiceClient(self, settings)
|
self._client = ServiceClient(settings.id, self, settings)
|
||||||
self._host = host
|
self._host = host
|
||||||
self._port = port
|
self._port = port
|
||||||
self._socketio = socketio
|
self._socketio = socketio
|
||||||
|
@ -69,7 +69,7 @@ class Service(IStreamHandler):
|
||||||
return self._client.stop_service(delay)
|
return self._client.stop_service(delay)
|
||||||
|
|
||||||
def get_log_service(self):
|
def get_log_service(self):
|
||||||
return self._client.get_log_service(self._host, self._port, self.id)
|
return self._client.get_log_service(self._host, self._port)
|
||||||
|
|
||||||
def ping(self):
|
def ping(self):
|
||||||
return self._client.ping_service()
|
return self._client.ping_service()
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from bson.objectid import ObjectId
|
||||||
|
|
||||||
from app.client.client import Client
|
from app.client.client import Client
|
||||||
from app.client.client_handler import IClientHandler
|
from app.client.client_handler import IClientHandler
|
||||||
from app.client.json_rpc import Request, Response
|
from app.client.json_rpc import Request, Response
|
||||||
|
@ -11,6 +13,8 @@ import app.constants as constants
|
||||||
class ServiceClient(IClientHandler):
|
class ServiceClient(IClientHandler):
|
||||||
HTTP_HOST = 'http_host'
|
HTTP_HOST = 'http_host'
|
||||||
VODS_HOST = 'vods_host'
|
VODS_HOST = 'vods_host'
|
||||||
|
SUBSCRIBERS_HOST = 'subscribers_host'
|
||||||
|
BANDWIDTH_HOST = 'bandwidth_host'
|
||||||
VERSION = 'version'
|
VERSION = 'version'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -25,7 +29,8 @@ class ServiceClient(IClientHandler):
|
||||||
def get_pipeline_stream_path(host: str, port: int, stream_id: str):
|
def get_pipeline_stream_path(host: str, port: int, stream_id: str):
|
||||||
return constants.DEFAULT_STREAM_PIPELINE_PATH_TEMPLATE_3SIS.format(host, port, stream_id)
|
return constants.DEFAULT_STREAM_PIPELINE_PATH_TEMPLATE_3SIS.format(host, port, stream_id)
|
||||||
|
|
||||||
def __init__(self, handler: IStreamHandler, settings: ServiceSettings):
|
def __init__(self, sid: ObjectId, handler: IStreamHandler, settings: ServiceSettings):
|
||||||
|
self.id = sid
|
||||||
self._request_id = 0
|
self._request_id = 0
|
||||||
self._handler = handler
|
self._handler = handler
|
||||||
self._service_settings = settings
|
self._service_settings = settings
|
||||||
|
@ -50,8 +55,9 @@ class ServiceClient(IClientHandler):
|
||||||
def stop_service(self, delay: int):
|
def stop_service(self, delay: int):
|
||||||
return self._client.stop_service(self._gen_request_id(), delay)
|
return self._client.stop_service(self._gen_request_id(), delay)
|
||||||
|
|
||||||
def get_log_service(self, host: str, port: int, sid: str):
|
def get_log_service(self, host: str, port: int):
|
||||||
return self._client.get_log_service(self._gen_request_id(), ServiceClient.get_log_service_path(host, port, sid))
|
return self._client.get_log_service(self._gen_request_id(),
|
||||||
|
ServiceClient.get_log_service_path(host, port, str(self.id)))
|
||||||
|
|
||||||
def start_stream(self, config: dict):
|
def start_stream(self, config: dict):
|
||||||
return self._client.start_stream(self._gen_request_id(), config)
|
return self._client.start_stream(self._gen_request_id(), config)
|
||||||
|
@ -74,7 +80,12 @@ class ServiceClient(IClientHandler):
|
||||||
streams = []
|
streams = []
|
||||||
for stream in self._service_settings.streams:
|
for stream in self._service_settings.streams:
|
||||||
streams.append(stream.config())
|
streams.append(stream.config())
|
||||||
return self._client.sync_service(self._gen_request_id(), streams)
|
|
||||||
|
subscribers = []
|
||||||
|
for subs in self._service_settings.subscribers:
|
||||||
|
subscribers.append(subs.to_service(self.id))
|
||||||
|
|
||||||
|
return self._client.sync_service(self._gen_request_id(), streams, subscribers)
|
||||||
|
|
||||||
def get_http_host(self) -> str:
|
def get_http_host(self) -> str:
|
||||||
return self._http_host
|
return self._http_host
|
||||||
|
@ -82,6 +93,12 @@ class ServiceClient(IClientHandler):
|
||||||
def get_vods_host(self) -> str:
|
def get_vods_host(self) -> str:
|
||||||
return self._vods_host
|
return self._vods_host
|
||||||
|
|
||||||
|
def get_subscribers_host(self) -> str:
|
||||||
|
return self._subscribers_host
|
||||||
|
|
||||||
|
def get_bandwidth_host(self) -> str:
|
||||||
|
return self._bandwidth_host
|
||||||
|
|
||||||
def get_vods_in(self) -> list:
|
def get_vods_in(self) -> list:
|
||||||
return self._vods_in
|
return self._vods_in
|
||||||
|
|
||||||
|
@ -98,7 +115,9 @@ class ServiceClient(IClientHandler):
|
||||||
self.sync_service()
|
self.sync_service()
|
||||||
if self._handler:
|
if self._handler:
|
||||||
self._set_runtime_fields(resp.result[ServiceClient.HTTP_HOST],
|
self._set_runtime_fields(resp.result[ServiceClient.HTTP_HOST],
|
||||||
resp.result[ServiceClient.VODS_HOST], resp.result[ServiceClient.VERSION])
|
resp.result[ServiceClient.VODS_HOST], resp.result[ServiceClient.VODS_HOST],
|
||||||
|
resp.result[ServiceClient.SUBSCRIBERS_HOST],
|
||||||
|
resp.result[ServiceClient.BANDWIDTH_HOST])
|
||||||
self._handler.on_service_statistic_received(resp.result)
|
self._handler.on_service_statistic_received(resp.result)
|
||||||
|
|
||||||
if req.method == Commands.PREPARE_SERVICE_COMMAND and resp.is_message():
|
if req.method == Commands.PREPARE_SERVICE_COMMAND and resp.is_message():
|
||||||
|
@ -127,9 +146,13 @@ class ServiceClient(IClientHandler):
|
||||||
self._handler.on_client_state_changed(status)
|
self._handler.on_client_state_changed(status)
|
||||||
|
|
||||||
# private
|
# private
|
||||||
def _set_runtime_fields(self, http_host=None, vods_host=None, version=None, vods_in=None):
|
def _set_runtime_fields(self, http_host=None, vods_host=None, subscribers_host=None, bandwidth_host=None,
|
||||||
|
version=None,
|
||||||
|
vods_in=None):
|
||||||
self._http_host = http_host
|
self._http_host = http_host
|
||||||
self._vods_host = vods_host
|
self._vods_host = vods_host
|
||||||
|
self._subscribers_host = subscribers_host
|
||||||
|
self._bandwidth_host = bandwidth_host
|
||||||
self._version = version
|
self._version = version
|
||||||
self._vods_in = vods_in
|
self._vods_in = vods_in
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ class ServiceSettings(Document, ServerSettings):
|
||||||
|
|
||||||
streams = ListField(EmbeddedDocumentField(Stream), default=[])
|
streams = ListField(EmbeddedDocumentField(Stream), default=[])
|
||||||
users = ListField(EmbeddedDocumentField(UserPair), default=[])
|
users = ListField(EmbeddedDocumentField(UserPair), default=[])
|
||||||
|
subscribers = ListField(ReferenceField('Subscriber'), default=[])
|
||||||
|
|
||||||
def generate_playlist(self) -> str:
|
def generate_playlist(self) -> str:
|
||||||
result = '#EXTM3U\n'
|
result = '#EXTM3U\n'
|
||||||
|
@ -47,4 +48,23 @@ class ServiceSettings(Document, ServerSettings):
|
||||||
for user in self.users:
|
for user in self.users:
|
||||||
if user.id == uid:
|
if user.id == uid:
|
||||||
self.users.remove(user)
|
self.users.remove(user)
|
||||||
|
break
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def add_subscriber(self, sid):
|
||||||
|
self.subscribers.append(sid)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def remove_subscriber(self, sid):
|
||||||
|
for subscriber in self.subscribers:
|
||||||
|
if subscriber == sid:
|
||||||
|
self.subscribers.remove(subscriber)
|
||||||
|
break
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def find_stream_settings_by_id(self, sid):
|
||||||
|
for stream in self.streams:
|
||||||
|
if stream.id == sid:
|
||||||
|
return stream
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
|
@ -6,9 +6,11 @@ from flask_login import login_required, current_user
|
||||||
|
|
||||||
from app import get_runtime_folder
|
from app import get_runtime_folder
|
||||||
from app.service.forms import ServiceSettingsForm, ActivateForm, UploadM3uForm, UserServerForm
|
from app.service.forms import ServiceSettingsForm, ActivateForm, UploadM3uForm, UserServerForm
|
||||||
|
from app.subscriber.forms import SubscriberForm
|
||||||
from app.service.service_entry import ServiceSettings, UserPair
|
from app.service.service_entry import ServiceSettings, UserPair
|
||||||
from app.utils.m3u_parser import M3uParser
|
from app.utils.m3u_parser import M3uParser
|
||||||
from app.home.user_loging_manager import User
|
from app.home.user_loging_manager import User
|
||||||
|
from app.subscriber.subscriber_entry import Subscriber
|
||||||
import app.constants as constants
|
import app.constants as constants
|
||||||
|
|
||||||
|
|
||||||
|
@ -157,11 +159,25 @@ class ServiceView(FlaskView):
|
||||||
|
|
||||||
return render_template('service/user/add.html', form=form)
|
return render_template('service/user/add.html', form=form)
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@route('/subscriber/add/<sid>', methods=['GET', 'POST'])
|
||||||
|
def subscriber_add(self, sid):
|
||||||
|
form = SubscriberForm(obj=Subscriber())
|
||||||
|
if request.method == 'POST' and form.validate_on_submit():
|
||||||
|
server = ServiceSettings.objects(id=sid).first()
|
||||||
|
if server:
|
||||||
|
new_entry = form.make_entry()
|
||||||
|
new_entry.add_server(server)
|
||||||
|
|
||||||
|
server.add_subscriber(new_entry)
|
||||||
|
return jsonify(status='ok'), 200
|
||||||
|
|
||||||
|
return render_template('service/subscriber/add.html', form=form)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@route('/add', methods=['GET', 'POST'])
|
@route('/add', methods=['GET', 'POST'])
|
||||||
def add(self):
|
def add(self):
|
||||||
model = ServiceSettings()
|
form = ServiceSettingsForm(obj=ServiceSettings())
|
||||||
form = ServiceSettingsForm(obj=model)
|
|
||||||
if request.method == 'POST' and form.validate_on_submit():
|
if request.method == 'POST' and form.validate_on_submit():
|
||||||
new_entry = form.make_entry()
|
new_entry = form.make_entry()
|
||||||
admin = UserPair(current_user.id, constants.Roles.ADMIN)
|
admin = UserPair(current_user.id, constants.Roles.ADMIN)
|
||||||
|
|
|
@ -65,6 +65,46 @@ class StreamFields:
|
||||||
TIMESTAMP = 'timestamp'
|
TIMESTAMP = 'timestamp'
|
||||||
|
|
||||||
|
|
||||||
|
class EpgInfo:
|
||||||
|
ID_FIELD = 'id'
|
||||||
|
URL_FIELD = 'url'
|
||||||
|
TITLE_FIELD = 'display_name'
|
||||||
|
ICON_FIELD = 'icon'
|
||||||
|
PROGRAMS_FIELD = 'programs'
|
||||||
|
|
||||||
|
id = str
|
||||||
|
url = str
|
||||||
|
title = str
|
||||||
|
icon = str
|
||||||
|
programs = []
|
||||||
|
|
||||||
|
def __init__(self, eid: str, url: str, title: str, icon: str, programs=list()):
|
||||||
|
self.id = eid
|
||||||
|
self.url = url
|
||||||
|
self.title = title
|
||||||
|
self.icon = icon
|
||||||
|
self.programs = programs
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {EpgInfo.ID_FIELD: self.id, EpgInfo.URL_FIELD: self.url, EpgInfo.TITLE_FIELD: self.title,
|
||||||
|
EpgInfo.ICON_FIELD: self.icon, EpgInfo.PROGRAMS_FIELD: self.programs}
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelInfo:
|
||||||
|
EPG_FIELD = 'epg'
|
||||||
|
VIDEO_ENABLE_FIELD = 'video'
|
||||||
|
AUDIO_ENABLE_FIELD = 'audio'
|
||||||
|
|
||||||
|
def __init__(self, epg: EpgInfo, have_video=True, have_audio=True):
|
||||||
|
self.have_video = have_video
|
||||||
|
self.have_audio = have_audio
|
||||||
|
self.epg = epg
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
return {ChannelInfo.EPG_FIELD: self.epg.to_dict(), ChannelInfo.VIDEO_ENABLE_FIELD: self.have_video,
|
||||||
|
ChannelInfo.AUDIO_ENABLE_FIELD: self.have_audio}
|
||||||
|
|
||||||
|
|
||||||
class Stream(EmbeddedDocument):
|
class Stream(EmbeddedDocument):
|
||||||
meta = {'allow_inheritance': True, 'auto_create_index': True}
|
meta = {'allow_inheritance': True, 'auto_create_index': True}
|
||||||
|
|
||||||
|
@ -159,6 +199,13 @@ class Stream(EmbeddedDocument):
|
||||||
conf[AUDIO_SELECT_FIELD] = audio_select
|
conf[AUDIO_SELECT_FIELD] = audio_select
|
||||||
return conf
|
return conf
|
||||||
|
|
||||||
|
def to_channel_info(self) -> [ChannelInfo]:
|
||||||
|
ch = []
|
||||||
|
for out in self.output.urls:
|
||||||
|
epg = EpgInfo(self.get_id(), out.uri, self.name, self.icon)
|
||||||
|
ch.append(ChannelInfo(epg))
|
||||||
|
return ch
|
||||||
|
|
||||||
def generate_feedback_dir(self):
|
def generate_feedback_dir(self):
|
||||||
return '{0}/{1}/{2}'.format(self._settings.feedback_directory, self.get_type(), self.get_id())
|
return '{0}/{1}/{2}'.format(self._settings.feedback_directory, self.get_type(), self.get_id())
|
||||||
|
|
||||||
|
|
26
app/subscriber/forms.py
Normal file
26
app/subscriber/forms.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from flask_babel import lazy_gettext
|
||||||
|
|
||||||
|
from wtforms.fields import StringField, PasswordField, SubmitField, SelectField
|
||||||
|
from wtforms.validators import InputRequired, Length, Email
|
||||||
|
|
||||||
|
from app.subscriber.subscriber_entry import Subscriber
|
||||||
|
|
||||||
|
|
||||||
|
class SubscriberForm(FlaskForm):
|
||||||
|
AVAILABLE_COUNTRIES = [('UK', 'United kingdom'), ('RU', 'Russian'), ('BY', 'Belarus')]
|
||||||
|
email = StringField(lazy_gettext(u'Email:'),
|
||||||
|
validators=[InputRequired(), Email(message=lazy_gettext(u'Invalid email')), Length(max=30)])
|
||||||
|
password = PasswordField(lazy_gettext(u'Password:'), validators=[InputRequired(), Length(min=4, max=80)])
|
||||||
|
country = SelectField(lazy_gettext(u'Locale:'), coerce=str, validators=[InputRequired()],
|
||||||
|
choices=AVAILABLE_COUNTRIES)
|
||||||
|
apply = SubmitField(lazy_gettext(u'Apply'))
|
||||||
|
|
||||||
|
def make_entry(self):
|
||||||
|
return self.update_entry(Subscriber())
|
||||||
|
|
||||||
|
def update_entry(self, subscriber: Subscriber):
|
||||||
|
subscriber.email = self.email.data
|
||||||
|
subscriber.password = Subscriber.make_md5_hash_from_password(self.password.data)
|
||||||
|
subscriber.country = self.country.data
|
||||||
|
return subscriber
|
105
app/subscriber/subscriber_entry.py
Normal file
105
app/subscriber/subscriber_entry.py
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from hashlib import md5
|
||||||
|
from bson.objectid import ObjectId
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
from mongoengine import Document, EmbeddedDocument, StringField, DateTimeField, IntField, ListField, ReferenceField, \
|
||||||
|
PULL, ObjectIdField, EmbeddedDocumentField
|
||||||
|
|
||||||
|
from app.service.service_entry import ServiceSettings
|
||||||
|
|
||||||
|
|
||||||
|
class Device(EmbeddedDocument):
|
||||||
|
DEFAULT_DEVICE_NAME = 'Device'
|
||||||
|
MIN_DEVICE_NAME_LENGTH = 3
|
||||||
|
MAX_DEVICE_NAME_LENGTH = 32
|
||||||
|
|
||||||
|
meta = {'allow_inheritance': False, 'auto_create_index': True}
|
||||||
|
id = ObjectIdField(required=True, default=ObjectId, unique=True, primary_key=True)
|
||||||
|
created_date = DateTimeField(default=datetime.now)
|
||||||
|
name = StringField(default=DEFAULT_DEVICE_NAME, min_length=MIN_DEVICE_NAME_LENGTH,
|
||||||
|
max_length=MAX_DEVICE_NAME_LENGTH,
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Subscriber(Document):
|
||||||
|
ID_FIELD = "id"
|
||||||
|
EMAIL_FIELD = "login"
|
||||||
|
PASSWORD_FIELD = "password"
|
||||||
|
STATUS_FIELD = "status"
|
||||||
|
DEVICES_FIELD = "devices"
|
||||||
|
STREAMS_FIELD = "channels"
|
||||||
|
|
||||||
|
class Status(IntEnum):
|
||||||
|
NO_ACTIVE = 0
|
||||||
|
ACTIVE = 1
|
||||||
|
BANNED = 2
|
||||||
|
|
||||||
|
class Type(IntEnum):
|
||||||
|
USER = 0,
|
||||||
|
SUPPORT = 1
|
||||||
|
|
||||||
|
SUBSCRIBER_HASH_LENGHT = 32
|
||||||
|
|
||||||
|
meta = {'collection': 'subscribers', 'auto_create_index': False}
|
||||||
|
|
||||||
|
email = StringField(max_length=30, required=True)
|
||||||
|
password = StringField(min_length=SUBSCRIBER_HASH_LENGHT, max_length=SUBSCRIBER_HASH_LENGHT, required=True)
|
||||||
|
created_date = DateTimeField(default=datetime.now)
|
||||||
|
status = IntField(default=Status.ACTIVE)
|
||||||
|
type = IntField(default=Type.USER)
|
||||||
|
country = StringField(min_length=2, max_length=3, required=True)
|
||||||
|
servers = ListField(ReferenceField(ServiceSettings, reverse_delete_rule=PULL), default=[])
|
||||||
|
devices = ListField(EmbeddedDocumentField(Device), default=[])
|
||||||
|
streams = ListField(ObjectIdField(), default=[])
|
||||||
|
|
||||||
|
def add_server(self, server: ServiceSettings):
|
||||||
|
self.servers.append(server)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def add_stream(self, stream):
|
||||||
|
self.streams.append(stream.id)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def remove_stream(self, sid: ObjectId):
|
||||||
|
for stream in self.streams:
|
||||||
|
if stream == sid:
|
||||||
|
self.streams.remove(stream)
|
||||||
|
break
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def to_service(self, sid: ObjectId) -> dict:
|
||||||
|
for serv in self.servers:
|
||||||
|
if serv.id == sid:
|
||||||
|
devices = []
|
||||||
|
for dev in self.devices:
|
||||||
|
devices.append(str(dev.id))
|
||||||
|
|
||||||
|
streams = []
|
||||||
|
for stream in self.streams:
|
||||||
|
founded_stream = serv.find_stream_settings_by_id(stream)
|
||||||
|
if founded_stream:
|
||||||
|
channels = founded_stream.to_channel_info()
|
||||||
|
for ch in channels:
|
||||||
|
streams.append(ch.to_dict())
|
||||||
|
|
||||||
|
conf = {
|
||||||
|
Subscriber.ID_FIELD: str(self.id), Subscriber.EMAIL_FIELD: self.email,
|
||||||
|
Subscriber.PASSWORD_FIELD: self.password, Subscriber.STATUS_FIELD: self.status,
|
||||||
|
Subscriber.DEVICES_FIELD: devices, Subscriber.STREAMS_FIELD: streams}
|
||||||
|
return conf
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def make_md5_hash_from_password(password: str) -> str:
|
||||||
|
m = md5()
|
||||||
|
m.update(password.encode())
|
||||||
|
return m.hexdigest()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def md5_user(cls, email: str, password: str, country: str):
|
||||||
|
return cls(email=email, password=Subscriber.make_md5_hash_from_password(password), country=country)
|
||||||
|
|
||||||
|
|
||||||
|
Subscriber.register_delete_rule(ServiceSettings, "subscribers", PULL)
|
|
@ -38,6 +38,10 @@
|
||||||
<br>
|
<br>
|
||||||
{{ render_bootstrap_form(form.vods_host) }}
|
{{ render_bootstrap_form(form.vods_host) }}
|
||||||
<br>
|
<br>
|
||||||
|
{{ render_bootstrap_form(form.subscribers_host) }}
|
||||||
|
<br>
|
||||||
|
{{ render_bootstrap_form(form.bandwidth_host) }}
|
||||||
|
<br>
|
||||||
{{ render_bootstrap_field(form.feedback_directory) }}
|
{{ render_bootstrap_field(form.feedback_directory) }}
|
||||||
<br>
|
<br>
|
||||||
{{ render_bootstrap_field(form.timeshifts_directory) }}
|
{{ render_bootstrap_field(form.timeshifts_directory) }}
|
||||||
|
|
4
app/templates/service/subscriber/add.html
Normal file
4
app/templates/service/subscriber/add.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends 'service/subscriber/base.html' %}
|
||||||
|
{% block title %}
|
||||||
|
Add subscriber to server
|
||||||
|
{% endblock %}
|
45
app/templates/service/subscriber/base.html
Normal file
45
app/templates/service/subscriber/base.html
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{% from 'bootstrap/wtf.html' import form_field %}
|
||||||
|
{% macro render_bootstrap_field(field) %}
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-md-4">{{ field.label }}</label>
|
||||||
|
<div class="col-md-8">
|
||||||
|
{{ field(class='form-control')|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro render_bootstrap_form(form) %}
|
||||||
|
<div class="row">
|
||||||
|
<label class="col-md-4">{{ form.label }}</label>
|
||||||
|
<div class="col-md-8">
|
||||||
|
{{ form() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
<form id="subscriber_entry_form" name="subscriber_entry_form" class="form" method="post">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title">
|
||||||
|
{% block title %}
|
||||||
|
{% endblock %}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<br>
|
||||||
|
{{ render_bootstrap_field(form.email) }}
|
||||||
|
<br>
|
||||||
|
{{ render_bootstrap_field(form.password) }}
|
||||||
|
<br>
|
||||||
|
{{ render_bootstrap_field(form.country) }}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
{% block footer %}
|
||||||
|
<button type="button" class="btn btn-danger" data-dismiss="modal">{% trans %}Cancel{% endtrans %}</button>
|
||||||
|
{{ form_field(form.apply, class="btn btn-success") }}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</form>
|
4
app/templates/service/subscriber/edit.html
Normal file
4
app/templates/service/subscriber/edit.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends 'service/subscriber/base.html' %}
|
||||||
|
{% block title %}
|
||||||
|
Edit subscriber
|
||||||
|
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends 'service/base.html' %}
|
{% extends 'service/user/base.html' %}
|
||||||
{% block title %}
|
{% block title %}
|
||||||
Edit service
|
Edit service
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -588,11 +588,12 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit_stream_entry(url) {
|
function edit_stream_entry(url) {
|
||||||
|
var data_ser = $('#stream_entry_form').serialize();
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
type: "POST",
|
type: "POST",
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
data: $('#stream_entry_form').serialize(),
|
data: data_ser,
|
||||||
success: function (response) {
|
success: function (response) {
|
||||||
console.log(response);
|
console.log(response);
|
||||||
$('#stream_dialog').modal('hide');
|
$('#stream_dialog').modal('hide');
|
||||||
|
@ -808,12 +809,5 @@ Dashboard | {{ config['PUBLIC_CONFIG'].site.title }}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -74,6 +74,10 @@ Settings | {{ config['PUBLIC_CONFIG'].site.title }}
|
||||||
onclick="add_user_to_server('{{ server.id }}')">
|
onclick="add_user_to_server('{{ server.id }}')">
|
||||||
{% trans %}Add user{% endtrans %}
|
{% trans %}Add user{% endtrans %}
|
||||||
</button>
|
</button>
|
||||||
|
<button type="submit" class="btn btn-success btn-xs"
|
||||||
|
onclick="add_subscriber_to_server('{{ server.id }}')">
|
||||||
|
{% trans %}Add subscriber{% endtrans %}
|
||||||
|
</button>
|
||||||
<button type="submit" class="btn btn-danger btn-xs"
|
<button type="submit" class="btn btn-danger btn-xs"
|
||||||
onclick="remove_server('{{ server.id }}')">
|
onclick="remove_server('{{ server.id }}')">
|
||||||
{% trans %}Remove{% endtrans %}
|
{% trans %}Remove{% endtrans %}
|
||||||
|
@ -155,6 +159,37 @@ Settings | {{ config['PUBLIC_CONFIG'].site.title }}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function add_subscriber_to_server_entry(url) {
|
||||||
|
$.ajax({
|
||||||
|
url: url,
|
||||||
|
type: "POST",
|
||||||
|
dataType: 'json',
|
||||||
|
data: $('#subscriber_entry_form').serialize(),
|
||||||
|
success: function (response) {
|
||||||
|
console.log(response);
|
||||||
|
$('#service_dialog').modal('hide');
|
||||||
|
window.location.reload();
|
||||||
|
},
|
||||||
|
error: function (error) {
|
||||||
|
console.error(error);
|
||||||
|
$('#service_dialog .modal-content').html(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_subscriber_to_server(sid) {
|
||||||
|
var url = "/service/subscriber/add/" + sid;
|
||||||
|
$.get(url, function(data) {
|
||||||
|
$('#service_dialog .modal-content').html(data);
|
||||||
|
$('#service_dialog').modal();
|
||||||
|
|
||||||
|
$('#apply').click(function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
add_subscriber_to_server_entry(url);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function add_server() {
|
function add_server() {
|
||||||
var url = "{{ url_for('ServiceView:add') }}";
|
var url = "{{ url_for('ServiceView:add') }}";
|
||||||
$.get(url, function(data) {
|
$.get(url, function(data) {
|
||||||
|
@ -215,7 +250,5 @@ Settings | {{ config['PUBLIC_CONFIG'].site.title }}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue