moved some models added cron and websocket base class
This commit is contained in:
94
backend/api/models.py
Normal file
94
backend/api/models.py
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
from flask_restplus import fields
|
||||||
|
from backend.api import api_user, api_recorder, api_v1
|
||||||
|
|
||||||
|
generic_id_parser = api_v1.parser()
|
||||||
|
generic_id_parser.add_argument('id', type=str, required=True, store_missing=False)
|
||||||
|
|
||||||
|
user_model = api_user.model('User', {
|
||||||
|
'id': fields.String(required=True, description='The user\'s identifier'),
|
||||||
|
'first_name': fields.String(required=True, description='The user\'s first name'),
|
||||||
|
'last_name': fields.String(required=True, description='The user\'s last name'),
|
||||||
|
'email': fields.String(required=True, description='The user\'s email address'),
|
||||||
|
'nickname': fields.String(required=False, description='The user\'s nick name'),
|
||||||
|
'last_seen': fields.DateTime(required=False, description='Last time user logged in'),
|
||||||
|
'last_time_modified': fields.DateTime(required=False, description='Last time user was modified'),
|
||||||
|
'role': fields.String(required=False, description='Role a user might have (in addition to group memberships)'),
|
||||||
|
'effective_permissions': fields.List(
|
||||||
|
fields.String(required=True), required=False, description="List of permissions (groups + (optional) role)."
|
||||||
|
),
|
||||||
|
'groups': fields.List(
|
||||||
|
fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})),
|
||||||
|
required=False, description='Group memberships.'),
|
||||||
|
'favorite_recorders': fields.List(
|
||||||
|
fields.Nested(api_user.model('favorite_recorder', {'id': fields.Integer(), 'name': fields.String()})),
|
||||||
|
required=False, description='Favorite recorders.'),
|
||||||
|
})
|
||||||
|
|
||||||
|
recorder_model = api_recorder.model('Recorder', {
|
||||||
|
'id': fields.String(required=False, description='The recorder\'s identifier'),
|
||||||
|
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
|
||||||
|
'last_time_modified': fields.DateTime(required=False, description='Creation date of the recorder'),
|
||||||
|
'name': fields.String(min_length=3, required=True, description='The recorder\'s name'),
|
||||||
|
'description': fields.String(required=False, description='The recorder\'s description'),
|
||||||
|
'locked': fields.Boolean(required=False, description='Indicates whether the recorder settings can be altered'),
|
||||||
|
'lock_message': fields.String(required=False, description='Optional: message explaining lock state'),
|
||||||
|
'offline': fields.Boolean(required=False,
|
||||||
|
description='Should be set when recorder is disconnected for maintenance, etc.'),
|
||||||
|
'ip': fields.String(required=False, description='The recorder\'s IP address'),
|
||||||
|
'network_name': fields.String(required=False, description='The recorder\'s network name'),
|
||||||
|
'ssh_port': fields.Integer(required=True, default=22, description='The recorder\'s SSH port number'),
|
||||||
|
'telnet_port': fields.Integer(required=True, default=23, description='The recorder\'s telnet port number'),
|
||||||
|
# 'use_telnet_instead_ssh': fields.Boolean(required=False, default=False,
|
||||||
|
# description='If this is set, telnet will be used instead of ssh. '
|
||||||
|
# 'This might require specific commands.'),
|
||||||
|
'recorder_model': fields.Nested(api_recorder.model('recorder_model',
|
||||||
|
{'id': fields.Integer(),
|
||||||
|
'name': fields.String(attribute="model_name", )}),
|
||||||
|
required=False,
|
||||||
|
allow_null=True,
|
||||||
|
skip_none=False,
|
||||||
|
description='Model of the recorder.'),
|
||||||
|
'room': fields.Nested(api_recorder.model('recorder_room',
|
||||||
|
{'id': fields.Integer(), 'name': fields.String(),
|
||||||
|
'number': fields.String(), 'alternate_name': fields.String()}),
|
||||||
|
r0equired=False,
|
||||||
|
allow_null=True,
|
||||||
|
skip_none=False,
|
||||||
|
description='Room in which the recorder is located.'),
|
||||||
|
'virtual_commands': fields.List(fields.Nested(api_recorder.model('recorder_virtual_commands',
|
||||||
|
{'id': fields.Integer(),
|
||||||
|
'name': fields.String()})))
|
||||||
|
})
|
||||||
|
|
||||||
|
recorder_command_model = api_recorder.model('Recorder Command', {
|
||||||
|
'id': fields.String(required=False, description='The recorder command\'s identifier'),
|
||||||
|
'name': fields.String(required=True, description='The recorder command\'s name'),
|
||||||
|
'alternative_name': fields.String(required=False, description='The recorder command\'s alternative name'),
|
||||||
|
'disabled': fields.Boolean(required=False, description='Indicates if the recorder command is disabled'),
|
||||||
|
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
|
||||||
|
'last_time_modified': fields.DateTime(required=False),
|
||||||
|
'description': fields.String(required=False, description='The recorder command\'s description'),
|
||||||
|
'parameters': fields.Raw(required=True, description='The recorder parameters'),
|
||||||
|
'recorder_model': fields.Nested(api_recorder.model('recorder_command_models',
|
||||||
|
{'id': fields.Integer(),
|
||||||
|
'name': fields.String(attribute="model_name", )})),
|
||||||
|
})
|
||||||
|
|
||||||
|
recorder_model_model = api_recorder.model('Recorder Model', {
|
||||||
|
'id': fields.String(required=False, description='The recorder model\'s identifier'),
|
||||||
|
'name': fields.String(attribute="model_name", required=True, description='The recorder model\'s name'),
|
||||||
|
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
|
||||||
|
'last_time_modified': fields.DateTime(required=False),
|
||||||
|
'notes': fields.String(required=False, description='The recorder model\'s notes'),
|
||||||
|
'requires_username': fields.Boolean(),
|
||||||
|
'requires_password': fields.Boolean(),
|
||||||
|
|
||||||
|
'recorders': fields.List(fields.Nested(api_recorder.model('recorder_model',
|
||||||
|
{'id': fields.Integer(),
|
||||||
|
'name': fields.String(attribute="model_name", ),
|
||||||
|
'network_name': fields.String(),
|
||||||
|
'ip': fields.String()})), required=False,
|
||||||
|
description='Model of the recorder.'),
|
||||||
|
'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands")
|
||||||
|
})
|
||||||
|
|
||||||
@@ -13,78 +13,11 @@ from flask_restplus import fields, Resource, inputs
|
|||||||
|
|
||||||
from backend import db, app
|
from backend import db, app
|
||||||
from backend.api import api_recorder
|
from backend.api import api_recorder
|
||||||
|
from backend.api.models import recorder_model, recorder_model_model, recorder_command_model
|
||||||
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
|
from backend.models.recorder_model import Recorder, RecorderModel, RecorderCommand
|
||||||
from backend.models.room_model import Room
|
from backend.models.room_model import Room
|
||||||
import backend.recorder_adapters as r_a
|
import backend.recorder_adapters as r_a
|
||||||
|
|
||||||
recorder_model = api_recorder.model('Recorder', {
|
|
||||||
'id': fields.String(required=False, description='The recorder\'s identifier'),
|
|
||||||
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
|
|
||||||
'last_time_modified': fields.DateTime(required=False, description='Creation date of the recorder'),
|
|
||||||
'name': fields.String(min_length=3, required=True, description='The recorder\'s name'),
|
|
||||||
'description': fields.String(required=False, description='The recorder\'s description'),
|
|
||||||
'locked': fields.Boolean(required=False, description='Indicates whether the recorder settings can be altered'),
|
|
||||||
'lock_message': fields.String(required=False, description='Optional: message explaining lock state'),
|
|
||||||
'offline': fields.Boolean(required=False,
|
|
||||||
description='Should be set when recorder is disconnected for maintenance, etc.'),
|
|
||||||
'ip': fields.String(required=False, description='The recorder\'s IP address'),
|
|
||||||
'network_name': fields.String(required=False, description='The recorder\'s network name'),
|
|
||||||
'ssh_port': fields.Integer(required=True, default=22, description='The recorder\'s SSH port number'),
|
|
||||||
'telnet_port': fields.Integer(required=True, default=23, description='The recorder\'s telnet port number'),
|
|
||||||
# 'use_telnet_instead_ssh': fields.Boolean(required=False, default=False,
|
|
||||||
# description='If this is set, telnet will be used instead of ssh. '
|
|
||||||
# 'This might require specific commands.'),
|
|
||||||
'recorder_model': fields.Nested(api_recorder.model('recorder_model',
|
|
||||||
{'id': fields.Integer(),
|
|
||||||
'name': fields.String(attribute="model_name", )}),
|
|
||||||
required=False,
|
|
||||||
allow_null=True,
|
|
||||||
skip_none=False,
|
|
||||||
description='Model of the recorder.'),
|
|
||||||
'room': fields.Nested(api_recorder.model('recorder_room',
|
|
||||||
{'id': fields.Integer(), 'name': fields.String(),
|
|
||||||
'number': fields.String(), 'alternate_name': fields.String()}),
|
|
||||||
r0equired=False,
|
|
||||||
allow_null=True,
|
|
||||||
skip_none=False,
|
|
||||||
description='Room in which the recorder is located.'),
|
|
||||||
'virtual_commands': fields.List(fields.Nested(api_recorder.model('recorder_virtual_commands',
|
|
||||||
{'id': fields.Integer(),
|
|
||||||
'name': fields.String()})))
|
|
||||||
})
|
|
||||||
|
|
||||||
recorder_command_model = api_recorder.model('Recorder Command', {
|
|
||||||
'id': fields.String(required=False, description='The recorder command\'s identifier'),
|
|
||||||
'name': fields.String(required=True, description='The recorder command\'s name'),
|
|
||||||
'alternative_name': fields.String(required=False, description='The recorder command\'s alternative name'),
|
|
||||||
'disabled': fields.Boolean(required=False, description='Indicates if the recorder command is disabled'),
|
|
||||||
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
|
|
||||||
'last_time_modified': fields.DateTime(required=False),
|
|
||||||
'description': fields.String(required=False, description='The recorder command\'s description'),
|
|
||||||
'parameters': fields.Raw(required=True, description='The recorder parameters'),
|
|
||||||
'recorder_model': fields.Nested(api_recorder.model('recorder_command_models',
|
|
||||||
{'id': fields.Integer(),
|
|
||||||
'name': fields.String(attribute="model_name", )})),
|
|
||||||
})
|
|
||||||
|
|
||||||
recorder_model_model = api_recorder.model('Recorder Model', {
|
|
||||||
'id': fields.String(required=False, description='The recorder model\'s identifier'),
|
|
||||||
'name': fields.String(attribute="model_name", required=True, description='The recorder model\'s name'),
|
|
||||||
'created_at': fields.DateTime(required=False, description='Creation date of the recorder'),
|
|
||||||
'last_time_modified': fields.DateTime(required=False),
|
|
||||||
'notes': fields.String(required=False, description='The recorder model\'s notes'),
|
|
||||||
'requires_username': fields.Boolean(),
|
|
||||||
'requires_password': fields.Boolean(),
|
|
||||||
|
|
||||||
'recorders': fields.List(fields.Nested(api_recorder.model('recorder_model',
|
|
||||||
{'id': fields.Integer(),
|
|
||||||
'name': fields.String(attribute="model_name", ),
|
|
||||||
'network_name': fields.String(),
|
|
||||||
'ip': fields.String()})), required=False,
|
|
||||||
description='Model of the recorder.'),
|
|
||||||
'commands': fields.List(fields.Nested(recorder_command_model), attribute="recorder_commands")
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
# ==
|
# ==
|
||||||
|
|
||||||
|
|||||||
@@ -8,28 +8,15 @@ Login through API does not start a new session, but instead returns JWT.
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
|
|
||||||
from flask_jwt_extended import get_jwt_identity, jwt_required, current_user
|
from flask_jwt_extended import get_jwt_identity, jwt_required, current_user
|
||||||
from flask_restplus import Resource, fields, inputs
|
from flask_restplus import Resource, fields, inputs, abort
|
||||||
|
|
||||||
from backend import db, app, jwt_auth
|
from backend import db, app, jwt_auth
|
||||||
from backend.api import api_user
|
from backend.api import api_user
|
||||||
|
from backend.api.models import user_model, recorder_model, generic_id_parser
|
||||||
|
from backend.models import Recorder
|
||||||
from backend.models.user_model import User, Group
|
from backend.models.user_model import User, Group
|
||||||
|
|
||||||
user_model = api_user.model('User', {
|
|
||||||
'id': fields.String(required=True, description='The user\'s identifier'),
|
|
||||||
'first_name': fields.String(required=True, description='The user\'s first name'),
|
|
||||||
'last_name': fields.String(required=True, description='The user\'s last name'),
|
|
||||||
'email': fields.String(required=True, description='The user\'s email address'),
|
|
||||||
'nickname': fields.String(required=False, description='The user\'s nick name'),
|
|
||||||
'role': fields.String(required=False, description='Role a user might have (in addition to group memberships)'),
|
|
||||||
'effective_permissions': fields.List(
|
|
||||||
fields.String(required=True), required=False, description="List of permissions (groups + (optional) role)."
|
|
||||||
),
|
|
||||||
'groups': fields.List(
|
|
||||||
fields.Nested(api_user.model('user_group', {'id': fields.Integer(), 'name': fields.String()})),
|
|
||||||
required=False, description='Group memberships.'),
|
|
||||||
})
|
|
||||||
|
|
||||||
user_update_parser = api_user.parser()
|
user_update_parser = api_user.parser()
|
||||||
user_update_parser.add_argument('email', type=inputs.email(), required=False, nullable=False, store_missing=False)
|
user_update_parser.add_argument('email', type=inputs.email(), required=False, nullable=False, store_missing=False)
|
||||||
@@ -64,6 +51,37 @@ class Profile(Resource):
|
|||||||
return "ok"
|
return "ok"
|
||||||
|
|
||||||
|
|
||||||
|
@api_user.route('/profile/favorite_recorders')
|
||||||
|
class UserFavoriteRecorders(Resource):
|
||||||
|
@jwt_required
|
||||||
|
@api_user.marshal_list_with(recorder_model)
|
||||||
|
def get(self):
|
||||||
|
try:
|
||||||
|
current_user_id = get_jwt_identity()
|
||||||
|
return User.get_by_identifier(current_user_id).favorite_recorders
|
||||||
|
except AttributeError:
|
||||||
|
abort(404, "User not found!")
|
||||||
|
|
||||||
|
@jwt_required
|
||||||
|
@api_user.expect(generic_id_parser)
|
||||||
|
@api_user.marshal_list_with(recorder_model)
|
||||||
|
def put(self):
|
||||||
|
try:
|
||||||
|
args = generic_id_parser.parse_args()
|
||||||
|
current_user_id = get_jwt_identity()
|
||||||
|
user = User.get_by_identifier(current_user_id)
|
||||||
|
print(user)
|
||||||
|
recorder = Recorder.get_by_identifier(args["id"])
|
||||||
|
print(recorder)
|
||||||
|
if recorder is None:
|
||||||
|
abort(404, "(Specified [id: {}]) recorder not found!".format(args["id"]))
|
||||||
|
user.favorite_recorders.append(recorder)
|
||||||
|
db.session.commit()
|
||||||
|
return user.favorite_recorders
|
||||||
|
except AttributeError:
|
||||||
|
abort(404, "User not found!")
|
||||||
|
|
||||||
|
|
||||||
@api_user.route('')
|
@api_user.route('')
|
||||||
class UserList(Resource):
|
class UserList(Resource):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
OIDC login auth module
|
OIDC login auth module
|
||||||
"""
|
"""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from flask import jsonify, redirect, url_for
|
from flask import jsonify, redirect, url_for
|
||||||
@@ -45,7 +46,9 @@ def create_or_retrieve_user_from_userinfo(userinfo):
|
|||||||
|
|
||||||
if user is not None:
|
if user is not None:
|
||||||
app.logger.info("user found")
|
app.logger.info("user found")
|
||||||
#TODO: update user!
|
user.last_seen = datetime.utcnow()
|
||||||
|
# TODO: update user!
|
||||||
|
db.session.commit()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
user = User(email=email, first_name=userinfo.get("given_name", ""),
|
user = User(email=email, first_name=userinfo.get("given_name", ""),
|
||||||
|
|||||||
7
backend/cron/__init__.py
Normal file
7
backend/cron/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
cron_log_handler = logging.FileHandler(CRON_LOG_FILE)
|
||||||
|
cron_logger = logging.getLogger("mal.cron")
|
||||||
|
cron_logger.addHandler(cron_log_handler)
|
||||||
|
logging.getLogger("apscheduler.scheduler").addHandler(cron_log_handler)
|
||||||
|
logging.getLogger("apscheduler.executors.default").addHandler(cron_log_handler)
|
||||||
@@ -93,6 +93,10 @@ class Recorder(db.Model):
|
|||||||
def get_by_name(name):
|
def get_by_name(name):
|
||||||
return Recorder.query.filter(Recorder.name == name).first()
|
return Recorder.query.filter(Recorder.name == name).first()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_by_identifier(identifier):
|
||||||
|
return Recorder.query.filter(Recorder.id == identifier).first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all():
|
def get_all():
|
||||||
return Recorder.query.all()
|
return Recorder.query.all()
|
||||||
|
|||||||
@@ -31,6 +31,18 @@ acquaintances = db.Table('acquaintances',
|
|||||||
db.Column('acquaintance_id', db.Integer, db.ForeignKey('user.id'))
|
db.Column('acquaintance_id', db.Integer, db.ForeignKey('user.id'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
user_favorite_recorders_table = db.Table('user_favorite_recorders',
|
||||||
|
db.Column('user_id', db.Integer,
|
||||||
|
db.ForeignKey('user.id',
|
||||||
|
onupdate="CASCADE",
|
||||||
|
ondelete="CASCADE"),
|
||||||
|
primary_key=True),
|
||||||
|
db.Column('recorder_id', db.Integer,
|
||||||
|
db.ForeignKey('recorder.id',
|
||||||
|
onupdate="CASCADE",
|
||||||
|
ondelete="CASCADE"),
|
||||||
|
primary_key=True))
|
||||||
|
|
||||||
# This is the association table for the many-to-many relationship between
|
# This is the association table for the many-to-many relationship between
|
||||||
# groups and members - this is, the memberships.
|
# groups and members - this is, the memberships.
|
||||||
user_group_table = db.Table('user_group',
|
user_group_table = db.Table('user_group',
|
||||||
@@ -97,6 +109,8 @@ class User(UserMixin, db.Model):
|
|||||||
backref=db.backref('followers', lazy='dynamic'),
|
backref=db.backref('followers', lazy='dynamic'),
|
||||||
lazy='dynamic')
|
lazy='dynamic')
|
||||||
|
|
||||||
|
favorite_recorders = db.relationship('Recorder', secondary=user_favorite_recorders_table)
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(User, self).__init__(**kwargs)
|
super(User, self).__init__(**kwargs)
|
||||||
password = kwargs.get("password", None)
|
password = kwargs.get("password", None)
|
||||||
|
|||||||
59
backend/websocket/base.py
Normal file
59
backend/websocket/base.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from flask_login import current_user
|
||||||
|
from flask_socketio import SocketIO, emit
|
||||||
|
|
||||||
|
from backend import app
|
||||||
|
|
||||||
|
logger = logging.getLogger("lrc.websocket")
|
||||||
|
|
||||||
|
async_mode = 'threading' # set to traditional python threading model
|
||||||
|
# async_mode = None
|
||||||
|
socketio = SocketIO(app, cors_allowed_origins="*", async_mode=async_mode)
|
||||||
|
|
||||||
|
|
||||||
|
class WebSocketBase:
|
||||||
|
def __init__(self, flask_app_context=None):
|
||||||
|
if flask_app_context is None:
|
||||||
|
self.flask_app_context = app
|
||||||
|
self.socket_thread = None
|
||||||
|
|
||||||
|
def start_websocket_in_thread(self, host=None, port=None, debug=None):
|
||||||
|
self.socket_thread = threading.Thread(
|
||||||
|
target=self.start_websocket,
|
||||||
|
args=(host, port, debug))
|
||||||
|
self.socket_thread.start()
|
||||||
|
return self.socket_thread
|
||||||
|
|
||||||
|
def start_websocket(self, host=None, port=None, debug=None):
|
||||||
|
if debug is None:
|
||||||
|
debug = self.flask_app_context.debug
|
||||||
|
socketio.run(self.flask_app_context, host=host, port=port, debug=debug)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@socketio.on('connect')
|
||||||
|
def connect_handler():
|
||||||
|
logger.debug("new connection...")
|
||||||
|
print(current_user)
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
logger.debug("user is authenticated")
|
||||||
|
print("allowed!")
|
||||||
|
emit('my response',
|
||||||
|
{'message': '{0} has joined'.format(current_user.name)},
|
||||||
|
broadcast=True)
|
||||||
|
else:
|
||||||
|
logger.info("user is not authenticated!")
|
||||||
|
print("not allowed!!")
|
||||||
|
return False # not allowed here
|
||||||
|
|
||||||
|
@socketio.on_error()
|
||||||
|
def handle_error(self, error):
|
||||||
|
logger.error(error)
|
||||||
|
print(error)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
wsb = WebSocketBase()
|
||||||
|
#wsb.start_websocket_in_thread(debug=True)
|
||||||
|
wsb.start_websocket(debug=True)
|
||||||
@@ -115,9 +115,13 @@ if __name__ == '__main__':
|
|||||||
# socketio.emit('server_event', "You!: server_event", namespace="/")
|
# socketio.emit('server_event', "You!: server_event", namespace="/")
|
||||||
# print("send bla")
|
# print("send bla")
|
||||||
|
|
||||||
socketio.run(app, host="localhost", port=5000, debug=True)
|
|
||||||
|
#socketio.run(app, host="localhost", port=5000, debug=True)
|
||||||
|
start_in_thread()
|
||||||
|
while True:
|
||||||
|
time.sleep(2)
|
||||||
|
print("still running! :)")
|
||||||
# socketio.run(app, debug=True)
|
# socketio.run(app, debug=True)
|
||||||
# ENDE
|
# ENDE
|
||||||
|
|
||||||
print("running?!")
|
|
||||||
# t.join()
|
# t.join()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ app.config['SECRET_KEY'] = 'secret!'
|
|||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
|
|
||||||
#socketio = SocketIO(message_queue="redis://")
|
#socketio = SocketIO(message_queue="redis://")
|
||||||
socketio = SocketIO(app)
|
socketio = SocketIO(app, port=5443, debug=True)
|
||||||
|
|
||||||
#socketio.run(app, host="localhost", port=5000)
|
#socketio.run(app, host="localhost", port=5000)
|
||||||
#socketio.init_app(app, host="localhost", port=5000, cors_allowed_origins="*", )
|
#socketio.init_app(app, host="localhost", port=5000, cors_allowed_origins="*", )
|
||||||
|
|||||||
Reference in New Issue
Block a user