1
0
Fork 0
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:
Alexandr Topilski 2019-06-11 07:16:46 +03:00 committed by GitHub
parent 197173dd66
commit df32bb51a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 388 additions and 28 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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'), ]

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -0,0 +1,4 @@
{% extends 'service/subscriber/base.html' %}
{% block title %}
Add subscriber to server
{% endblock %}

View 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">&times;</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>

View file

@ -0,0 +1,4 @@
{% extends 'service/subscriber/base.html' %}
{% block title %}
Edit subscriber
{% endblock %}

View file

@ -1,4 +1,4 @@
{% extends 'service/base.html' %} {% extends 'service/user/base.html' %}
{% block title %} {% block title %}
Edit service Edit service
{% endblock %} {% endblock %}

View file

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

View file

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