updated and removed unecesary files

This commit is contained in:
2025-03-25 14:56:11 +02:00
parent be11dbfc43
commit 74f5670f46
29 changed files with 63 additions and 740 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

184
app.py
View File

@@ -1,13 +1,13 @@
import os
from flask import Flask, render_template, request, redirect, url_for, session, flash, jsonify, send_from_directory
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from flask_bcrypt import Bcrypt
from werkzeug.utils import secure_filename
from functools import wraps
from flask_migrate import Migrate
from pdf2image import convert_from_path
import subprocess
from werkzeug.utils import secure_filename
from functools import wraps
from extensions import db, bcrypt, login_manager
from models import User, Player, Content # Import the models from models.py
from flask_login import login_user, logout_user, login_required, current_user
app = Flask(__name__, instance_relative_config=True)
@@ -21,8 +21,9 @@ app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Ensure the instance folder exists
os.makedirs(app.instance_path, exist_ok=True)
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
db.init_app(app)
bcrypt.init_app(app)
login_manager.init_app(app)
UPLOAD_FOLDER = 'static/uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@@ -34,7 +35,6 @@ if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
os.makedirs(UPLOAD_FOLDERLOGO)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
migrate = Migrate(app, db)
@@ -43,42 +43,6 @@ migrate = Migrate(app, db)
def load_user(user_id):
return User.query.get(int(user_id))
# Modele pentru baza de date
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(20), nullable=False, default='user')
theme = db.Column(db.String(10), nullable=False, default='light')
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
hostname = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
quickconnect_password = db.Column(db.String(120), nullable=False)
def verify_quickconnect_code(self, code):
return bcrypt.check_password_hash(self.quickconnect_password, code)
class Group(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True, nullable=False)
players = db.relationship('Player', secondary='group_player', backref='groups')
group_player = db.Table('group_player',
db.Column('group_id', db.Integer, db.ForeignKey('group.id'), primary_key=True),
db.Column('player_id', db.Integer, db.ForeignKey('player.id'), primary_key=True)
)
class Content(db.Model):
id = db.Column(db.Integer, primary_key=True)
file_name = db.Column(db.String(120), nullable=False)
duration = db.Column(db.Integer, nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True)
def admin_required(f):
@wraps(f)
def decorated_function(*args, **kwargs):
@@ -95,18 +59,16 @@ def convert_ppt_to_pdf(input_file, output_file):
@login_required
def dashboard():
players = Player.query.all()
groups = Group.query.all()
logo_exists = os.path.exists(os.path.join(app.config['UPLOAD_FOLDERLOGO'], 'logo.png'))
return render_template('dashboard.html', players=players, groups=groups, logo_exists=logo_exists)
return render_template('dashboard.html', players=players, logo_exists=logo_exists)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
email = request.form['email']
password = request.form['password']
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
new_user = User(username=username, email=email, password=hashed_password, role='user')
new_user = User(username=username, password=hashed_password, role='user')
db.session.add(new_user)
db.session.commit()
return redirect(url_for('login'))
@@ -157,7 +119,7 @@ def upload_content():
image_filename = f"{os.path.splitext(filename)[0]}_page_{i + 1}.png"
image_path = os.path.join(app.config['UPLOAD_FOLDER'], image_filename)
image.save(image_path, 'PNG')
new_content = Content(file_name=image_filename, duration=duration, player_id=target_id if target_type == 'player' else None, group_id=target_id if target_type == 'group' else None)
new_content = Content(file_name=image_filename, duration=duration, player_id=target_id if target_type == 'player' else None)
db.session.add(new_content)
os.remove(file_path) # Remove the original PDF file
elif media_type == 'ppt':
@@ -171,12 +133,12 @@ def upload_content():
image_filename = f"{os.path.splitext(filename)[0]}_page_{i + 1}.png"
image_path = os.path.join(app.config['UPLOAD_FOLDER'], image_filename)
image.save(image_path, 'PNG')
new_content = Content(file_name=image_filename, duration=duration, player_id=target_id if target_type == 'player' else None, group_id=target_id if target_type == 'group' else None)
new_content = Content(file_name=image_filename, duration=duration, player_id=target_id if target_type == 'player' else None)
db.session.add(new_content)
os.remove(file_path) # Remove the original PPT/PPTX file
os.remove(pdf_path) # Remove the intermediate PDF file
else:
new_content = Content(file_name=filename, duration=duration, player_id=target_id if target_type == 'player' else None, group_id=target_id if target_type == 'group' else None)
new_content = Content(file_name=filename, duration=duration, player_id=target_id if target_type == 'player' else None)
db.session.add(new_content)
db.session.commit()
@@ -186,8 +148,7 @@ def upload_content():
target_id = request.args.get('target_id')
return_url = request.args.get('return_url', url_for('dashboard'))
players = Player.query.all()
groups = Group.query.all()
return render_template('upload_content.html', target_type=target_type, target_id=target_id, players=players, groups=groups, return_url=return_url)
return render_template('upload_content.html', target_type=target_type, target_id=target_id, players=players, return_url=return_url)
@app.route('/admin')
@login_required
@@ -222,91 +183,14 @@ def delete_user(user_id):
@admin_required
def create_user():
username = request.form['username']
email = request.form['email']
password = request.form['password']
role = request.form['role']
hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
new_user = User(username=username, email=email, password=hashed_password, role=role)
new_user = User(username=username, password=hashed_password, role=role)
db.session.add(new_user)
db.session.commit()
return redirect(url_for('admin'))
@app.route('/group/<int:group_id>/manage')
@login_required
@admin_required
def manage_group(group_id):
group = Group.query.get_or_404(group_id)
available_players = Player.query.filter(~Player.groups.any(Group.id == group_id)).all()
return render_template('manage_group.html', group=group, available_players=available_players)
@app.route('/group/<int:group_id>/add_player', methods=['POST'])
@login_required
@admin_required
def add_player_to_group(group_id):
group = Group.query.get_or_404(group_id)
player_id = request.form['player_id']
player = Player.query.get_or_404(player_id)
group.players.append(player)
db.session.commit()
return redirect(url_for('manage_group', group_id=group_id))
@app.route('/group/<int:group_id>/remove_player/<int:player_id>', methods=['POST'])
@login_required
@admin_required
def remove_player_from_group(group_id, player_id):
group = Group.query.get_or_404(group_id)
player = Player.query.get_or_404(player_id)
group.players.remove(player)
db.session.commit()
return redirect(url_for('manage_group', group_id=group_id))
@app.route('/group/<int:group_id>/upload', methods=['POST'])
@login_required
@admin_required
def upload_content_to_group(group_id):
group = Group.query.get_or_404(group_id)
files = request.files.getlist('files')
duration = int(request.form['duration'])
for file in files:
filename = secure_filename(file.filename)
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
new_content = Content(file_name=filename, duration=duration, group_id=group_id)
db.session.add(new_content)
db.session.commit()
return redirect(url_for('manage_group', group_id=group_id))
@app.route('/group/content/<int:content_id>/edit', methods=['POST'])
@login_required
@admin_required
def edit_group_content(content_id):
content = Content.query.get_or_404(content_id)
new_duration = int(request.form['duration'])
content.duration = new_duration
db.session.commit()
return redirect(url_for('manage_group', group_id=content.group_id))
@app.route('/group/content/<int:content_id>/delete', methods=['POST'])
@login_required
@admin_required
def delete_group_content(content_id):
content = Content.query.get_or_404(content_id)
group_id = content.group_id
db.session.delete(content)
db.session.commit()
return redirect(url_for('manage_group', group_id=group_id))
@app.route('/group/<int:group_id>/delete', methods=['POST'])
@login_required
@admin_required
def delete_group(group_id):
group = db.session.get(Group, group_id)
db.session.delete(group)
db.session.commit()
return redirect(url_for('dashboard'))
@app.route('/player/<int:player_id>')
@login_required
def player_page(player_id):
@@ -372,24 +256,11 @@ def player_fullscreen(player_id):
authenticated = False
if authenticated or current_user.is_authenticated:
if player.groups:
# If the player is part of a group, get the group's content
group = player.groups[0] # Assuming a player can only be in one group
content = Content.query.filter_by(group_id=group.id).all()
else:
# If the player is not part of a group, get the player's content
content = Content.query.filter_by(player_id=player_id).all()
content = Content.query.filter_by(player_id=player_id).all()
return render_template('player_fullscreen.html', player=player, content=content)
else:
return render_template('player_auth.html', player_id=player_id)
@app.route('/group/<int:group_id>/fullscreen')
@login_required
def group_fullscreen(group_id):
group = Group.query.get_or_404(group_id)
content = Content.query.filter_by(group_id=group.id).all()
return render_template('group_fullscreen.html', group=group, content=content)
@app.route('/player/<int:player_id>/delete', methods=['POST'])
@login_required
@admin_required
@@ -422,25 +293,12 @@ def add_player():
return redirect(url_for('dashboard'))
return render_template('add_player.html')
@app.route('/group/add', methods=['GET', 'POST'])
@login_required
@admin_required
def add_group():
if request.method == 'POST':
name = request.form['name']
new_group = Group(name=name)
db.session.add(new_group)
db.session.commit()
return redirect(url_for('dashboard'))
return render_template('add_group.html')
@app.route('/integrate_player')
@login_required
@admin_required
def integrate_player():
players = Player.query.all()
groups = Group.query.all()
return render_template('integrate_player.html', players=players, groups=groups)
return render_template('integrate_player.html', players=players)
@app.route('/edit_player/<int:player_id>', methods=['GET', 'POST'])
@login_required
@@ -549,13 +407,7 @@ def get_playlist():
if not player or not player.verify_quickconnect_code(quickconnect_code):
return jsonify({'error': 'Invalid hostname or quick connect code'}), 404
if player.groups:
# If the player is part of a group, get the group's content
group = player.groups[0] # Assuming a player can only be in one group
content = Content.query.filter_by(group_id=group.id).all()
else:
# If the player is not part of a group, get the player's content
content = Content.query.filter_by(player_id=player.id).all()
content = Content.query.filter_by(player_id=player.id).all()
playlist = [{'file_name': item.file_name, 'duration': item.duration, 'url': url_for('media', filename=item.file_name, _external=True)} for item in content]
return jsonify({'playlist': playlist})

View File

@@ -1,10 +1,7 @@
# drop_user_table.py
from app import app, db
def clear_database():
with app.app_context():
db.drop_all()
db.create_all()
print("Database cleared and structure recreated.")
if __name__ == '__main__':
clear_database()
with app.app_context():
db.drop_all()
print("Dropped all tables.")

8
extensions.py Normal file
View File

@@ -0,0 +1,8 @@
# extensions.py
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager
db = SQLAlchemy()
bcrypt = Bcrypt()
login_manager = LoginManager()

View File

@@ -1,19 +0,0 @@
import requests
# Replace with the actual server IP address or domain name and quick connect code
server_ip = 'http://192.168.0.115:5000'
quickconnect_code = 'Initial01!'
# Construct the URL with the quick connect code as a query parameter
url = f'{server_ip}/api/playlists?quickconnect_code={quickconnect_code}'
# Make the GET request to the API
response = requests.get(url)
# Check if the request was successful
if response.status_code == 200:
# Parse the JSON response
playlist = response.json().get('playlist', [])
print('Playlist:', playlist)
else:
print('Failed to retrieve playlist:', response.json())

View File

@@ -17,4 +17,5 @@ def create_admin_user():
if __name__ == '__main__':
with app.app_context():
db.create_all()
create_admin_user()
create_admin_user()

Binary file not shown.

View File

@@ -1 +0,0 @@
Single-database configuration for Flask.

View File

@@ -1,86 +0,0 @@
[alembic]
# path to migration scripts
script_location = migrations
# template used to generate migration files
file_template = %%(rev)s_%%(slug)s
# timezone to use when rendering the date within the migration file as well as the filename.
# string value is passed to datetime.datetime.strftime(), default is to use UTC.
# output_timezone = UTC
# max length of characters to apply to the "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without a source .py file to be detected as revisions
# sourceless = false
# version location specification; this defaults
# to migrations/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "os.pathsep".
# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = sqlite:///dashboard.db
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# hooks =
# format the generated revision script to be PEP8 compliant
# hooks = pep8
# python
# hook_1.type = python
# hook_1.entrypoint = myproject.please_run_pep8
# hook_1.options = {"treat-as-warning": "true"}
# exec
# hook_2.type = exec
# hook_2.entrypoint = /usr/bin/pep8
# hook_2.options =
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers = console
qualname = sqlalchemy.engine
# propagate = 0
[logger_alembic]
level = INFO
handlers = console
qualname = alembic
# propagate = 0
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

View File

@@ -1,75 +0,0 @@
from __future__ import with_statement
import logging
from logging.config import fileConfig
from sqlalchemy import engine_from_config, pool
from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

View File

@@ -1,24 +0,0 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@@ -1,28 +0,0 @@
"""Add theme column to user table
Revision ID: 173774695298
Revises: c07c6e720021
Create Date: 2025-01-23 15:16:46.761912
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '173774695298'
down_revision = 'c07c6e720021'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,28 +0,0 @@
"""Add theme column to user table
Revision ID: c07c6e720021
Revises: e341b0e3043c
Create Date: 2025-01-23 15:13:01.413532
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'c07c6e720021'
down_revision = 'e341b0e3043c'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,72 +0,0 @@
"""Initial migration
Revision ID: e341b0e3043c
Revises:
Create Date: 2025-01-23 15:09:25.485823
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e341b0e3043c'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('group',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=80), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('player',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=80), nullable=False),
sa.Column('hostname', sa.String(length=120), nullable=False),
sa.Column('password', sa.String(length=120), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('hostname'),
sa.UniqueConstraint('username')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=80), nullable=False),
sa.Column('password', sa.String(length=120), nullable=False),
sa.Column('role', sa.String(length=20), nullable=False),
sa.Column('theme', sa.String(length=10), nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('username')
)
op.create_table('content',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('file_name', sa.String(length=120), nullable=False),
sa.Column('duration', sa.Integer(), nullable=False),
sa.Column('player_id', sa.Integer(), nullable=True),
sa.Column('group_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['group_id'], ['group.id'], ),
sa.ForeignKeyConstraint(['player_id'], ['player.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('group_players',
sa.Column('group_id', sa.Integer(), nullable=False),
sa.Column('player_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['group_id'], ['group.id'], ),
sa.ForeignKeyConstraint(['player_id'], ['player.id'], ),
sa.PrimaryKeyConstraint('group_id', 'player_id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('group_players')
op.drop_table('content')
op.drop_table('user')
op.drop_table('player')
op.drop_table('group')
# ### end Alembic commands ###

View File

@@ -1,5 +1,6 @@
from app import db
from extensions import db
from flask_bcrypt import Bcrypt
from flask_login import UserMixin
bcrypt = Bcrypt()
@@ -8,7 +9,6 @@ class Content(db.Model):
file_name = db.Column(db.String(120), nullable=False)
duration = db.Column(db.Integer, nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('player.id'), nullable=True)
group_id = db.Column(db.Integer, db.ForeignKey('group.id'), nullable=True)
class Player(db.Model):
id = db.Column(db.Integer, primary_key=True)
@@ -20,4 +20,32 @@ class Player(db.Model):
def verify_quickconnect_code(self, code):
return bcrypt.check_password_hash(self.quickconnect_password, code)
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password = db.Column(db.String(120), nullable=False)
role = db.Column(db.String(80), nullable=False)
theme = db.Column(db.String(80), default='light')
def set_password(self, password):
self.password = bcrypt.generate_password_hash(password).decode('utf-8')
def check_password(self, password):
return bcrypt.check_password_hash(self.password, password)
@property
def is_active(self):
return True
@property
def is_authenticated(self):
return True
@property
def is_anonymous(self):
return False
def get_id(self):
return str(self.id)
# other models...

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 KiB

View File

@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Add Group</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body.dark-mode {
background-color: #121212;
color: #ffffff;
}
.card.dark-mode {
background-color: #1e1e1e;
color: #ffffff;
}
.dark-mode label, .dark-mode th, .dark-mode td {
color: #ffffff;
}
</style>
</head>
<body class="{{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="container py-5">
<h1 class="text-center mb-4">Add Group</h1>
<form action="{{ url_for('add_group') }}" method="post">
<div class="mb-3">
<label for="name" class="form-label">Group Name</label>
<input type="text" class="form-control" id="name" name="name" required>
</div>
<div class="mb-3">
<label for="players" class="form-label">Select Players</label><br>
{% for player in players %}
<div class="form-check">
<input class="form-check-input" type="checkbox" name="players" value="{{ player.id }}" id="player{{ player.id }}">
<label class="form-check-label" for="player{{ player.id }}">
{{ player.username }}
</label>
</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary">Create Group</button>
</form>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary mt-3">Back to Dashboard</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -68,45 +68,6 @@
</div>
</div>
<!-- Groups Section -->
<div class="card mb-4 {{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="card-header bg-success text-white">
<h2>Player Groups</h2>
</div>
<div class="card-body">
<ul class="list-group">
{% for group in groups %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ group.name }}</strong> ({{ group.players | length }} players)
<ul class="list-group mt-2">
{% for player in group.players %}
<li class="list-group-item">
{{ player.username }}
</li>
{% endfor %}
</ul>
</div>
<div>
<a href="{{ url_for('manage_group', group_id=group.id) }}" class="btn btn-sm btn-secondary">Manage Group</a>
<a href="{{ url_for('group_fullscreen', group_id=group.id) }}" class="btn btn-sm btn-primary">Full Screen</a>
{% if current_user.role == 'admin' %}
<form action="{{ url_for('delete_group', group_id=group.id) }}" method="post" style="display:inline;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to delete this group?');">Delete</button>
</form>
{% endif %}
</div>
</li>
{% endfor %}
</ul>
{% if current_user.role == 'admin' %}
<div class="mt-3">
<a href="{{ url_for('add_group') }}" class="btn btn-primary">Add Group</a>
</div>
{% endif %}
</div>
</div>
<!-- Content Upload Section -->
<div class="card mb-4 {{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="card-header bg-warning text-dark">

View File

@@ -1,66 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Group Fullscreen Schedule</title>
<style>
body, html {
height: 100%;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: black;
}
img, video {
max-width: 100%;
max-height: 100%;
display: none;
}
.active {
display: block;
}
</style>
</head>
<body>
<div id="content">
{% for item in content %}
{% if item.file_name.endswith('.mp4') %}
<video class="content-item" data-duration="{{ item.duration }}" controls>
<source src="{{ url_for('static', filename='uploads/' ~ item.file_name) }}" type="video/mp4">
Your browser does not support the video tag.
</video>
{% else %}
<img src="{{ url_for('static', filename='uploads/' ~ item.file_name) }}" alt="Content Image" class="content-item" data-duration="{{ item.duration }}">
{% endif %}
{% endfor %}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const items = document.querySelectorAll('.content-item');
let currentIndex = 0;
function showNextItem() {
items.forEach(item => item.classList.remove('active'));
const currentItem = items[currentIndex];
currentItem.classList.add('active');
const duration = parseInt(currentItem.getAttribute('data-duration'), 10) * 1000;
if (currentItem.tagName === 'VIDEO') {
currentItem.play();
currentItem.onended = () => {
currentIndex = (currentIndex + 1) % items.length;
showNextItem();
};
} else {
setTimeout(() => {
currentIndex = (currentIndex + 1) % items.length;
showNextItem();
}, duration);
}
}
showNextItem();
});
</script>
</body>
</html>

View File

@@ -1,80 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Manage Group</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body.dark-mode {
background-color: #121212;
color: #ffffff;
}
.card.dark-mode {
background-color: #1e1e1e;
color: #ffffff;
}
.dark-mode label, .dark-mode th, .dark-mode td {
color: #ffffff;
}
.logo {
max-height: 100px;
margin-right: 20px;
}
</style>
</head>
<body class="{{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="container py-5">
<div class="d-flex justify-content-start align-items-center mb-4">
{% if logo_exists %}
<img src="{{ url_for('static', filename='resurse/logo.png') }}" alt="Logo" class="logo">
{% endif %}
<h1 class="mb-0">Manage Group: {{ group.name }}</h1>
</div>
<!-- Add Players to Group Section -->
<div class="card mb-4 {{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="card-header">
<h2>Add Players to Group</h2>
</div>
<div class="card-body">
<form action="{{ url_for('add_player_to_group', group_id=group.id) }}" method="post">
<div class="mb-3">
<label for="player_id" class="form-label">Select Player</label>
<select class="form-select {{ 'dark-mode' if theme == 'dark' else '' }}" id="player_id" name="player_id" required>
{% for player in available_players %}
<option value="{{ player.id }}">{{ player.username }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Add Player</button>
</form>
</div>
</div>
<!-- Group Players Section -->
<div class="card mb-4 {{ 'dark-mode' if theme == 'dark' else '' }}">
<div class="card-header">
<h2>Group Players</h2>
</div>
<div class="card-body">
<ul class="list-group">
{% for player in group.players %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ player.username }}</strong>
</div>
<div>
<form action="{{ url_for('remove_player_from_group', group_id=group.id, player_id=player.id) }}" method="post" style="display:inline;">
<button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure you want to remove this player from the group?');">Remove</button>
</form>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<a href="{{ url_for('dashboard') }}" class="btn btn-secondary">Back to Dashboard</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>