diff --git a/app/__init__.py b/app/__init__.py index 65df519..b943155 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -522,7 +522,7 @@ def create_app(config_name=None): from .routes.admin_management import bp as admin_management_bp app.register_blueprint(admin_management_bp, url_prefix='/admin/settings/admins') from .routes.role_management import bp as role_management_bp - app.register_blueprint(role_management_bp, url_prefix='/admin/settings/roles') + app.register_blueprint(role_management_bp, url_prefix='/admin/settings/admin/roles') from .routes.users import bp as users_bp app.register_blueprint(users_bp, url_prefix='/admin/users') from .routes.admin_user import admin_user_bp diff --git a/app/routes/settings.py b/app/routes/settings.py index 9aca11e..fea7a33 100644 --- a/app/routes/settings.py +++ b/app/routes/settings.py @@ -32,7 +32,7 @@ def index(): # The first one the user has access to will be their destination. permission_map = [ ('manage_general_settings', 'settings.general'), - ('manage_general_settings', 'settings.user_accounts'), + ('manage_users_general', 'settings.users_general'), ('view_admins_tab', 'admin_management.index'), ('view_admins_tab', 'role_management.index'), # Use same perm for both admin tabs ('manage_discord_settings', 'settings.discord'), @@ -99,31 +99,6 @@ def general(): active_tab='general' ) -@bp.route('/user_accounts', methods=['GET', 'POST']) -@login_required -@setup_required -@permission_required('manage_general_settings') -def user_accounts(): - # Redirect AppUsers without admin permissions away from admin pages - if current_user.userType == UserType.LOCAL and not current_user.has_permission('manage_general_settings'): - flash('You do not have permission to access the user accounts settings page.', 'danger') - return redirect(url_for('user.index')) - from app.forms import UserAccountsSettingsForm - form = UserAccountsSettingsForm() - if form.validate_on_submit(): - Setting.set('ALLOW_USER_ACCOUNTS', form.allow_user_accounts.data, SettingValueType.BOOLEAN, "Allow User Accounts") - - log_event(EventType.SETTING_CHANGE, "User account settings updated.", admin_id=current_user.id) - flash('User account settings saved successfully.', 'success') - return redirect(url_for('settings.user_accounts')) - elif request.method == 'GET': - form.allow_user_accounts.data = Setting.get_bool('ALLOW_USER_ACCOUNTS', False) - return render_template( - 'settings/index.html', - title="User Account Settings", - form=form, - active_tab='user_accounts' - ) @bp.route('/account', methods=['GET', 'POST']) @login_required @@ -746,4 +721,80 @@ def api_debug_execute(): return jsonify({'error': f'Request error: {str(e)}'}), 500 except Exception as e: current_app.logger.error(f"API Debug error: {e}") - return jsonify({'error': f'Unexpected error: {str(e)}'}), 500 \ No newline at end of file + return jsonify({'error': f'Unexpected error: {str(e)}'}), 500 + + +# Users General Management Routes +@bp.route('/users/general', methods=['GET', 'POST']) +@login_required +@setup_required +@permission_required('manage_users_general') +def users_general(): + """Display users general settings page""" + from app.forms import UserAccountsSettingsForm + form = UserAccountsSettingsForm() + + if form.validate_on_submit(): + # Handle user account settings + Setting.set('ALLOW_USER_ACCOUNTS', form.allow_user_accounts.data, SettingValueType.BOOLEAN, "Allow User Accounts") + + log_event(EventType.SETTING_CHANGE, "User account settings updated.", admin_id=current_user.id) + flash('User account settings saved successfully.', 'success') + return redirect(url_for('settings.users_general')) + elif request.method == 'GET': + # Load current settings from database + form.allow_user_accounts.data = Setting.get_bool('ALLOW_USER_ACCOUNTS', False) + + return render_template( + 'settings/index.html', + title="Users General Settings", + form=form, + active_tab='users_general' + ) + +# User Roles Management Routes +@bp.route('/users/roles') +@login_required +@setup_required +@permission_required('manage_user_roles') +def user_roles(): + """Display user roles management page""" + # For now, return empty list until user roles model is implemented + user_roles = [] + + return render_template( + 'settings/index.html', + title="User Role Management", + user_roles=user_roles, + active_tab='user_roles' + ) + +@bp.route('/users/roles/create') +@login_required +@setup_required +@permission_required('create_user_role') +def create_user_role(): + """Create new user role page""" + # Placeholder for future implementation + flash('User role creation is not yet implemented.', 'info') + return redirect(url_for('settings.user_roles')) + +@bp.route('/users/roles//edit') +@login_required +@setup_required +@permission_required('edit_user_role') +def edit_user_role(role_id): + """Edit user role page""" + # Placeholder for future implementation + flash('User role editing is not yet implemented.', 'info') + return redirect(url_for('settings.user_roles')) + +@bp.route('/users/roles//delete', methods=['POST']) +@login_required +@setup_required +@permission_required('delete_user_role') +def delete_user_role(role_id): + """Delete user role""" + # Placeholder for future implementation + flash('User role deletion is not yet implemented.', 'info') + return redirect(url_for('settings.user_roles')) \ No newline at end of file diff --git a/app/static/css/output.css b/app/static/css/output.css index 99b4da0..ba27502 100644 --- a/app/static/css/output.css +++ b/app/static/css/output.css @@ -4393,6 +4393,10 @@ .rounded-xl { border-radius: var(--radius-xl); } + .rounded-l { + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; + } .border { border-style: var(--tw-border-style); border-width: 1px; @@ -4919,6 +4923,12 @@ background-color: color-mix(in oklab, var(--color-base-300) 70%, transparent); } } + .bg-base-content\/20 { + background-color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + background-color: color-mix(in oklab, var(--color-base-content) 20%, transparent); + } + } .bg-black { background-color: var(--color-black); } @@ -4958,6 +4968,9 @@ background-color: color-mix(in oklab, var(--color-blue-500) 20%, transparent); } } + .bg-blue-600 { + background-color: var(--color-blue-600); + } .bg-blue-600\/20 { background-color: color-mix(in srgb, oklch(54.6% 0.245 262.881) 20%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -5069,6 +5082,9 @@ .bg-indigo-50 { background-color: var(--color-indigo-50); } + .bg-indigo-500 { + background-color: var(--color-indigo-500); + } .bg-indigo-500\/20 { background-color: color-mix(in srgb, oklch(58.5% 0.233 277.117) 20%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -5339,6 +5355,9 @@ background-color: color-mix(in oklab, var(--color-secondary) 20%, transparent); } } + .bg-slate-500 { + background-color: var(--color-slate-500); + } .bg-slate-500\/20 { background-color: color-mix(in srgb, oklch(55.4% 0.046 257.417) 20%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -5612,6 +5631,10 @@ --tw-gradient-to: var(--color-blue-100); --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); } + .to-blue-500 { + --tw-gradient-to: var(--color-blue-500); + --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); + } .to-blue-500\/10 { --tw-gradient-to: color-mix(in srgb, oklch(62.3% 0.214 259.815) 10%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -6681,6 +6704,9 @@ --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); } + .ring-accent { + --tw-ring-color: var(--color-accent); + } .ring-accent\/40 { --tw-ring-color: var(--color-accent); @supports (color: color-mix(in lab, red, red)) { @@ -6696,6 +6722,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-amber-600) 20%, transparent); } } + .ring-audiobookshelf { + --tw-ring-color: var(--color-audiobookshelf); + } .ring-audiobookshelf-600 { --tw-ring-color: var(--color-audiobookshelf-600); } @@ -6714,6 +6743,9 @@ .ring-base-300 { --tw-ring-color: var(--color-base-300); } + .ring-blue-500 { + --tw-ring-color: var(--color-blue-500); + } .ring-blue-500\/20 { --tw-ring-color: color-mix(in srgb, oklch(62.3% 0.214 259.815) 20%, transparent); @supports (color: color-mix(in lab, red, red)) { @@ -6729,6 +6761,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-blue-600) 20%, transparent); } } + .ring-emby { + --tw-ring-color: var(--color-emby); + } .ring-emby-600 { --tw-ring-color: var(--color-emby-600); } @@ -6744,6 +6779,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-emby) 20%, transparent); } } + .ring-error { + --tw-ring-color: var(--color-error); + } .ring-error\/40 { --tw-ring-color: var(--color-error); @supports (color: color-mix(in lab, red, red)) { @@ -6777,12 +6815,18 @@ --tw-ring-color: color-mix(in oklab, var(--color-indigo-600) 20%, transparent); } } + .ring-info { + --tw-ring-color: var(--color-info); + } .ring-info\/40 { --tw-ring-color: var(--color-info); @supports (color: color-mix(in lab, red, red)) { --tw-ring-color: color-mix(in oklab, var(--color-info) 40%, transparent); } } + .ring-jellyfin { + --tw-ring-color: var(--color-jellyfin); + } .ring-jellyfin-600 { --tw-ring-color: var(--color-jellyfin-600); } @@ -6798,6 +6842,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-jellyfin) 20%, transparent); } } + .ring-kavita { + --tw-ring-color: var(--color-kavita); + } .ring-kavita-600 { --tw-ring-color: var(--color-kavita-600); } @@ -6813,6 +6860,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-kavita) 20%, transparent); } } + .ring-komga { + --tw-ring-color: var(--color-komga); + } .ring-komga-600 { --tw-ring-color: var(--color-komga-600); } @@ -6837,6 +6887,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-orange-600) 20%, transparent); } } + .ring-plex { + --tw-ring-color: var(--color-plex); + } .ring-plex-600 { --tw-ring-color: var(--color-plex-600); } @@ -6891,6 +6944,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-red-600) 20%, transparent); } } + .ring-romm { + --tw-ring-color: var(--color-romm); + } .ring-romm-600 { --tw-ring-color: var(--color-romm-600); } @@ -6906,6 +6962,9 @@ --tw-ring-color: color-mix(in oklab, var(--color-romm) 20%, transparent); } } + .ring-secondary { + --tw-ring-color: var(--color-secondary); + } .ring-secondary\/40 { --tw-ring-color: var(--color-secondary); @supports (color: color-mix(in lab, red, red)) { @@ -7376,6 +7435,16 @@ } } } + .hover\:border-primary\/30 { + &:hover { + @media (hover: hover) { + border-color: var(--color-primary); + @supports (color: color-mix(in lab, red, red)) { + border-color: color-mix(in oklab, var(--color-primary) 30%, transparent); + } + } + } + } .hover\:bg-\[\#00a4dc\]\/10 { &:hover { @media (hover: hover) { diff --git a/app/templates/base.html b/app/templates/base.html index cdf5a1a..cff1fd8 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -92,7 +92,24 @@