From 67e03beddb901b1d2d5d58a007fc1dc90f128d55 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Dec 2025 14:22:19 +0000 Subject: [PATCH] Major web admin enhancements for full client management Stop command fix: - Use double-stop pattern (stop, check port, stop again if needed) - Print commands being run for debugging Web Admin UI improvements: - Sort devices by extension number - Add Category dropdown to change device category from device list - Rooms page: card layout with vertical member list, delete room button - Categories page: card layout with vertical device list, move-to dropdown - Add Room modal and functionality - Add Category modal and functionality - Consistent formatting across all pages (card layout, tables) - Remove from room/category via buttons on those pages New API endpoints: - PUT /api/devices/{ext}/category - change device category - POST /api/rooms - create new room - DELETE /api/rooms/{ext} - delete room - POST /api/categories - create new category - DELETE /api/categories/{id} - delete category Python functions added: - change_device_category() - create_room(), delete_room() - create_category(), delete_category() Goal: Web admin can now fully replace the script for client management --- easy-asterisk-v0.10.0.sh | 632 +++++++++++++++++++++++++++++++++------ 1 file changed, 540 insertions(+), 92 deletions(-) diff --git a/easy-asterisk-v0.10.0.sh b/easy-asterisk-v0.10.0.sh index 6adfdb2..c890bdc 100644 --- a/easy-asterisk-v0.10.0.sh +++ b/easy-asterisk-v0.10.0.sh @@ -4090,6 +4090,136 @@ def remove_device_from_room(room_ext, device_ext): return update_room_members(room_ext, new_members) return False, "Room not found" +def change_device_category(extension, new_category): + """Change a device's category in pjsip.conf""" + if not os.path.exists(PJSIP_CONF): + return False, "Config file not found" + + with open(PJSIP_CONF, 'r') as f: + lines = f.readlines() + + new_lines = [] + found = False + in_device = False + + for line in lines: + stripped = line.strip() + + # Match device comment line: ; === Device: Name (category) === + if stripped.startswith('; === Device:') and f'[{extension}]' in ''.join(lines[lines.index(line):lines.index(line)+3]): + # Parse and update category in comment + match = re.match(r'^; === Device: (.+?) \(([^)]+)\)(.*?)===', stripped) + if match: + name = match.group(1) + old_cat = match.group(2) + rest = match.group(3) + new_lines.append(f'; === Device: {name} ({new_category}){rest}===\n') + found = True + in_device = True + continue + + new_lines.append(line) + + if found: + with open(PJSIP_CONF, 'w') as f: + f.writelines(new_lines) + subprocess.run(['asterisk', '-rx', 'pjsip reload'], capture_output=True) + return True, "Category changed" + return False, "Device not found" + +def create_room(extension, name, room_type='ring', timeout='60'): + """Create a new room in rooms.conf""" + if not os.path.exists(ROOMS_FILE): + with open(ROOMS_FILE, 'w') as f: + f.write('# Format: ext|name|members|timeout|type(ring/page)\n') + + # Check if extension already exists + with open(ROOMS_FILE, 'r') as f: + for line in f: + if line.strip() and not line.startswith('#'): + parts = line.split('|') + if len(parts) >= 1 and parts[0] == extension: + return False, "Room extension already exists" + + # Add new room + with open(ROOMS_FILE, 'a') as f: + f.write(f'{extension}|{name}||{timeout}|{room_type}\n') + + # Rebuild dialplan + subprocess.run(['/usr/local/bin/easy-asterisk', '--rebuild-dialplan'], capture_output=True) + return True, "Room created" + +def delete_room(extension): + """Delete a room from rooms.conf""" + if not os.path.exists(ROOMS_FILE): + return False, "Rooms file not found" + + with open(ROOMS_FILE, 'r') as f: + lines = f.readlines() + + new_lines = [] + found = False + for line in lines: + stripped = line.strip() + if stripped and not stripped.startswith('#'): + parts = stripped.split('|') + if len(parts) >= 1 and parts[0] == extension: + found = True + continue + new_lines.append(line) + + if found: + with open(ROOMS_FILE, 'w') as f: + f.writelines(new_lines) + subprocess.run(['/usr/local/bin/easy-asterisk', '--rebuild-dialplan'], capture_output=True) + return True, "Room deleted" + return False, "Room not found" + +def create_category(cat_id, name, auto_answer='', description=''): + """Create a new category in categories.conf""" + if not os.path.exists(CATEGORIES_FILE): + with open(CATEGORIES_FILE, 'w') as f: + f.write('# Format: id|name|auto_answer|description\n') + + # Check if category already exists + with open(CATEGORIES_FILE, 'r') as f: + for line in f: + if line.strip() and not line.startswith('#'): + parts = line.split('|') + if len(parts) >= 1 and parts[0] == cat_id: + return False, "Category ID already exists" + + # Add new category + with open(CATEGORIES_FILE, 'a') as f: + f.write(f'{cat_id}|{name}|{auto_answer}|{description}\n') + + return True, "Category created" + +def delete_category(cat_id): + """Delete a category from categories.conf""" + if not os.path.exists(CATEGORIES_FILE): + return False, "Categories file not found" + + with open(CATEGORIES_FILE, 'r') as f: + lines = f.readlines() + + new_lines = [] + found = False + for line in lines: + stripped = line.strip() + if stripped and not stripped.startswith('#'): + parts = stripped.split('|') + if len(parts) >= 1 and parts[0] == cat_id: + found = True + continue + new_lines.append(line) + + if found: + with open(CATEGORIES_FILE, 'w') as f: + f.writelines(new_lines) + return True, "Category deleted" + return False, "Category not found" + def generate_password(length=16): """Generate a random password""" import secrets @@ -4379,22 +4509,12 @@ HTML_TEMPLATE = '''

Rooms (Ring/Page Groups)

- -
-
- - - - - - - - - - - -
ExtensionNameTypeMembersTimeout
+
+ + +
+
@@ -4402,21 +4522,12 @@ HTML_TEMPLATE = '''

Device Categories

- -
-
- - - - - - - - - - -
IDNameAuto-AnswerDescription
+
+ + +
+
@@ -4503,9 +4614,84 @@ HTML_TEMPLATE = ''' + + + + + +