From 4c6e2fe1ec6dac067f80db8faa3e951d2031830f Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Tue, 17 Mar 2026 07:40:14 -0700 Subject: [PATCH] Revamp automation page: 2-col grid, duplicate, search/filter, templates, grouping - Switch user automations to 2-column grid layout (matches system automations) - Add duplicate button on non-system cards with POST /api/automations//duplicate - Add search/filter bar (text search + trigger/action dropdowns) shown at 6+ automations - Add Inspiration section with 8 starter templates that pre-fill the builder - Add folder-style automation grouping with group_name DB column, dropdown popover for assignment, collapsible group sections, and builder group input --- database/music_database.py | 22 ++- web_server.py | 37 ++++- webui/index.html | 8 ++ webui/static/script.js | 278 ++++++++++++++++++++++++++++++++++++- webui/static/style.css | 96 ++++++++++++- 5 files changed, 431 insertions(+), 10 deletions(-) diff --git a/database/music_database.py b/database/music_database.py index f1d7eecb..7aa8de65 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -445,6 +445,7 @@ class MusicDatabase: self._add_automation_notify_columns(cursor) self._add_automation_system_column(cursor) self._add_automation_then_actions_column(cursor) + self._add_automation_group_name_column(cursor) # Library issues — user-reported problems with tracks/albums/artists cursor.execute(""" @@ -520,6 +521,17 @@ class MusicDatabase: except Exception as e: logger.error(f"Error adding automation system column: {e}") + def _add_automation_group_name_column(self, cursor): + """Add group_name column to automations table for folder-style grouping.""" + try: + cursor.execute("PRAGMA table_info(automations)") + cols = [c[1] for c in cursor.fetchall()] + if 'group_name' not in cols: + cursor.execute("ALTER TABLE automations ADD COLUMN group_name TEXT DEFAULT NULL") + logger.info("Added group_name column to automations table") + except Exception as e: + logger.error(f"Error adding automation group_name column: {e}") + def _add_automation_then_actions_column(self, cursor): """Add then_actions column to automations table and migrate existing notify data.""" try: @@ -8322,15 +8334,15 @@ class MusicDatabase: def create_automation(self, name: str, trigger_type: str, trigger_config: str, action_type: str, action_config: str, profile_id: int = 1, notify_type: str = None, notify_config: str = '{}', - then_actions: str = '[]'): + then_actions: str = '[]', group_name: str = None): """Create a new automation. Returns the new automation ID or None.""" try: with self._get_connection() as conn: cursor = conn.cursor() cursor.execute(""" - INSERT INTO automations (name, trigger_type, trigger_config, action_type, action_config, profile_id, notify_type, notify_config, then_actions) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - """, (name, trigger_type, trigger_config, action_type, action_config, profile_id, notify_type, notify_config, then_actions)) + INSERT INTO automations (name, trigger_type, trigger_config, action_type, action_config, profile_id, notify_type, notify_config, then_actions, group_name) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + """, (name, trigger_type, trigger_config, action_type, action_config, profile_id, notify_type, notify_config, then_actions, group_name)) conn.commit() return cursor.lastrowid except Exception as e: @@ -8377,7 +8389,7 @@ class MusicDatabase: def update_automation(self, automation_id: int, **kwargs) -> bool: """Update automation fields.""" - allowed = {'name', 'enabled', 'trigger_type', 'trigger_config', 'action_type', 'action_config', 'next_run', 'notify_type', 'notify_config', 'last_result', 'is_system', 'then_actions'} + allowed = {'name', 'enabled', 'trigger_type', 'trigger_config', 'action_type', 'action_config', 'next_run', 'notify_type', 'notify_config', 'last_result', 'is_system', 'then_actions', 'group_name'} updates = {k: v for k, v in kwargs.items() if k in allowed} if not updates: return False diff --git a/web_server.py b/web_server.py index a37e3a53..f610f445 100644 --- a/web_server.py +++ b/web_server.py @@ -4790,8 +4790,9 @@ def create_automation(): if cycle: return jsonify({"error": f"Signal cycle detected: {' → '.join(cycle)}. This would cause an infinite loop."}), 400 + group_name = data.get('group_name') or None db = get_database() - auto_id = db.create_automation(name, trigger_type, trigger_config, action_type, action_config, profile_id, notify_type, notify_config, then_actions_json) + auto_id = db.create_automation(name, trigger_type, trigger_config, action_type, action_config, profile_id, notify_type, notify_config, then_actions_json, group_name) if auto_id is None: return jsonify({"error": "Failed to create automation"}), 500 @@ -4864,6 +4865,8 @@ def update_automation_endpoint(automation_id): update_fields['notify_type'] = data['notify_type'] or None if 'notify_config' in data and 'then_actions' not in data: update_fields['notify_config'] = json.dumps(data['notify_config']) + if 'group_name' in data: + update_fields['group_name'] = data['group_name'] or None if not update_fields: return jsonify({"error": "No fields to update"}), 400 @@ -4927,6 +4930,38 @@ def delete_automation_endpoint(automation_id): logger.error(f"Error deleting automation: {e}") return jsonify({"error": str(e)}), 500 +@app.route('/api/automations//duplicate', methods=['POST']) +def duplicate_automation_endpoint(automation_id): + """Duplicate an automation. System automations cannot be duplicated.""" + try: + db = get_database() + auto = db.get_automation(automation_id) + if not auto: + return jsonify({"error": "Automation not found"}), 404 + if auto.get('is_system'): + return jsonify({"error": "System automations cannot be duplicated"}), 403 + profile_id = session.get('profile_id', 1) + new_id = db.create_automation( + name=f"{auto['name']} (Copy)", + trigger_type=auto['trigger_type'], + trigger_config=auto.get('trigger_config', '{}'), + action_type=auto['action_type'], + action_config=auto.get('action_config', '{}'), + profile_id=profile_id, + notify_type=auto.get('notify_type'), + notify_config=auto.get('notify_config', '{}'), + then_actions=auto.get('then_actions', '[]'), + group_name=auto.get('group_name'), + ) + if new_id is None: + return jsonify({"error": "Failed to duplicate automation"}), 500 + if automation_engine: + automation_engine.schedule_automation(new_id) + return jsonify({"success": True, "id": new_id}) + except Exception as e: + logger.error(f"Error duplicating automation: {e}") + return jsonify({"error": str(e)}), 500 + @app.route('/api/automations//toggle', methods=['POST']) def toggle_automation_endpoint(automation_id): """Toggle an automation's enabled state.""" diff --git a/webui/index.html b/webui/index.html index bb8bd865..a8427378 100644 --- a/webui/index.html +++ b/webui/index.html @@ -2339,6 +2339,12 @@
+