Merge pull request #25 from outis1one/claude/fix-extension-connectivity-QNhcq

Add rename for rooms/categories, fix stop command output
This commit is contained in:
outis1one
2025-12-23 16:51:16 -05:00
committed by GitHub
+195 -18
View File
@@ -4175,6 +4175,35 @@ def delete_room(extension):
return True, "Room deleted" return True, "Room deleted"
return False, "Room not found" return False, "Room not found"
def rename_room(extension, new_name):
"""Rename a room in 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) >= 5 and parts[0] == extension:
# Update name (parts[1])
parts[1] = new_name
new_lines.append('|'.join(parts) + '\n')
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 renamed"
return False, "Room not found"
def create_category(cat_id, name, auto_answer='', description=''): def create_category(cat_id, name, auto_answer='', description=''):
"""Create a new category in categories.conf""" """Create a new category in categories.conf"""
if not os.path.exists(CATEGORIES_FILE): if not os.path.exists(CATEGORIES_FILE):
@@ -4220,6 +4249,34 @@ def delete_category(cat_id):
return True, "Category deleted" return True, "Category deleted"
return False, "Category not found" return False, "Category not found"
def rename_category(cat_id, new_name):
"""Rename a category in 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) >= 2 and parts[0] == cat_id:
# Update name (parts[1])
parts[1] = new_name
new_lines.append('|'.join(parts) + '\n')
found = True
continue
new_lines.append(line)
if found:
with open(CATEGORIES_FILE, 'w') as f:
f.writelines(new_lines)
return True, "Category renamed"
return False, "Category not found"
def generate_password(length=16): def generate_password(length=16):
"""Generate a random password""" """Generate a random password"""
import secrets import secrets
@@ -4399,10 +4456,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
.btn-primary { background: var(--primary); color: white; } .btn-primary { background: var(--primary); color: white; }
.btn-danger { background: var(--danger); color: white; } .btn-danger { background: var(--danger); color: white; }
.btn-sm { padding: 4px 10px; font-size: 0.8rem; } .btn-sm { padding: 4px 10px; font-size: 0.8rem; }
.action-select { width: 95px; padding: 5px 4px; font-size: 12px; border: 1px solid #ccc; border-radius: 4px; } .action-select { width: 100px; padding: 5px 4px; font-size: 12px; border: 1px solid #ccc; border-radius: 4px; margin-right: 4px; }
.action-btn { width: 65px; padding: 5px 4px; font-size: 12px; } .action-btn { width: 100px; padding: 5px 4px; font-size: 12px; margin-right: 4px; }
.sort-control { display: flex; align-items: center; gap: 8px; } .sort-control { display: inline-flex; align-items: center; gap: 8px; margin-right: 15px; }
.sort-control label { font-size: 13px; color: #666; } .sort-control label { font-size: 13px; color: #666; white-space: nowrap; }
.sort-control select { padding: 5px 8px; font-size: 13px; border: 1px solid #ccc; border-radius: 4px; } .sort-control select { padding: 5px 8px; font-size: 13px; border: 1px solid #ccc; border-radius: 4px; }
.form-group { margin-bottom: 15px; } .form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: 500; } .form-group label { display: block; margin-bottom: 5px; font-weight: 500; }
@@ -4938,8 +4995,11 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
<span style="color:#666;margin-left:10px">(${c.id})</span> <span style="color:#666;margin-left:10px">(${c.id})</span>
${c.auto_answer ? `<span style="background:#e0e0e0;padding:2px 6px;border-radius:3px;margin-left:10px;font-size:12px">AA: ${c.auto_answer}</span>` : ''} ${c.auto_answer ? `<span style="background:#e0e0e0;padding:2px 6px;border-radius:3px;margin-left:10px;font-size:12px">AA: ${c.auto_answer}</span>` : ''}
</div> </div>
<div>
<button class="btn btn-primary btn-sm" onclick="showRenameCategoryModal('${c.id}', '${c.name}')" style="margin-right:5px">Rename</button>
<button class="btn btn-danger btn-sm" onclick="deleteCategory('${c.id}')" ${catDevices.length > 0 ? 'disabled title="Remove all devices first"' : ''}>Delete</button> <button class="btn btn-danger btn-sm" onclick="deleteCategory('${c.id}')" ${catDevices.length > 0 ? 'disabled title="Remove all devices first"' : ''}>Delete</button>
</div> </div>
</div>
${c.description ? `<p style="color:#666;margin:5px 0 0 0">${c.description}</p>` : ''} ${c.description ? `<p style="color:#666;margin:5px 0 0 0">${c.description}</p>` : ''}
${deviceRows} ${deviceRows}
</div> </div>
@@ -4995,7 +5055,10 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
<span style="background:${r.type === 'page' ? '#dcfce7' : '#dbeafe'};padding:2px 6px;border-radius:3px;margin-left:10px;font-size:12px">${r.type}</span> <span style="background:${r.type === 'page' ? '#dcfce7' : '#dbeafe'};padding:2px 6px;border-radius:3px;margin-left:10px;font-size:12px">${r.type}</span>
<span style="color:#666;margin-left:10px">${r.timeout}s timeout</span> <span style="color:#666;margin-left:10px">${r.timeout}s timeout</span>
</div> </div>
<button class="btn btn-danger btn-sm" onclick="deleteRoom('${r.extension}')">Delete Room</button> <div>
<button class="btn btn-primary btn-sm" onclick="showRenameRoomModal('${r.extension}', '${r.name}')" style="margin-right:5px">Rename</button>
<button class="btn btn-danger btn-sm" onclick="deleteRoom('${r.extension}')">Delete</button>
</div>
</div> </div>
${memberRows} ${memberRows}
</div> </div>
@@ -5056,6 +5119,36 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
} }
} }
// Room rename
let renameRoomExt = '';
function showRenameRoomModal(ext, currentName) {
renameRoomExt = ext;
const name = prompt('Enter new name for room ' + ext + ':', currentName);
if (name && name.trim()) {
renameRoom(ext, name.trim());
}
}
async function renameRoom(ext, newName) {
try {
const res = await fetch(API_BASE + '/rooms/' + ext, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ name: newName })
});
const result = await res.json();
if (result.success) {
showAlert('Room renamed');
loadRooms();
loadDevices();
} else {
showAlert(result.error || 'Failed to rename room', 'error');
}
} catch (e) {
showAlert('Failed to rename room', 'error');
}
}
// Category modal functions // Category modal functions
function showAddCategoryModal() { document.getElementById('add-category-modal').classList.add('active'); } function showAddCategoryModal() { document.getElementById('add-category-modal').classList.add('active'); }
function closeAddCategoryModal() { document.getElementById('add-category-modal').classList.remove('active'); document.getElementById('add-category-form').reset(); } function closeAddCategoryModal() { document.getElementById('add-category-modal').classList.remove('active'); document.getElementById('add-category-form').reset(); }
@@ -5106,6 +5199,34 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
} }
} }
// Category rename
function showRenameCategoryModal(id, currentName) {
const name = prompt('Enter new name for category ' + id + ':', currentName);
if (name && name.trim()) {
renameCategory(id, name.trim());
}
}
async function renameCategory(id, newName) {
try {
const res = await fetch(API_BASE + '/categories/' + id, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ name: newName })
});
const result = await res.json();
if (result.success) {
showAlert('Category renamed');
loadCategories();
loadDevices();
} else {
showAlert(result.error || 'Failed to rename category', 'error');
}
} catch (e) {
showAlert('Failed to rename category', 'error');
}
}
function showAddModal() { function showAddModal() {
document.getElementById('add-modal').classList.add('active'); document.getElementById('add-modal').classList.add('active');
} }
@@ -5431,6 +5552,38 @@ class WebAdminHandler(http.server.BaseHTTPRequestHandler):
self.send_json({'success': False, 'error': str(e)}, 400) self.send_json({'success': False, 'error': str(e)}, 400)
return return
# Rename room: PUT /api/rooms/{ext}
room_rename_match = re.match(r'/api/rooms/(\d+)$', path)
if room_rename_match:
ext = room_rename_match.group(1)
try:
data = json.loads(body)
new_name = data.get('name', '').strip()
if not new_name:
self.send_json({'success': False, 'error': 'Name required'}, 400)
return
success, msg = rename_room(ext, new_name)
self.send_json({'success': success, 'message': msg})
except Exception as e:
self.send_json({'success': False, 'error': str(e)}, 400)
return
# Rename category: PUT /api/categories/{id}
cat_rename_match = re.match(r'/api/categories/([a-z0-9]+)$', path)
if cat_rename_match:
cat_id = cat_rename_match.group(1)
try:
data = json.loads(body)
new_name = data.get('name', '').strip()
if not new_name:
self.send_json({'success': False, 'error': 'Name required'}, 400)
return
success, msg = rename_category(cat_id, new_name)
self.send_json({'success': success, 'message': msg})
except Exception as e:
self.send_json({'success': False, 'error': str(e)}, 400)
return
self.send_response(404) self.send_response(404)
self.end_headers() self.end_headers()
@@ -5576,35 +5729,59 @@ web_admin_menu() {
;; ;;
2) 2)
print_info "Stopping web admin..." print_info "Stopping web admin..."
echo ""
# Check what's on the port first
echo " $ netstat -tlnp | grep ${WEB_ADMIN_PORT}"
netstat -tlnp 2>/dev/null | grep "${WEB_ADMIN_PORT}" || echo " (nothing found)"
echo ""
# First stop attempt # First stop attempt
echo " Running: systemctl stop easy-asterisk-webadmin" echo " $ systemctl stop easy-asterisk-webadmin"
systemctl stop easy-asterisk-webadmin 2>/dev/null || true systemctl stop easy-asterisk-webadmin 2>&1 || true
sleep 1 sleep 1
# Check if port is still in use # Check port again
if lsof -ti ":${WEB_ADMIN_PORT}" >/dev/null 2>&1; then echo ""
echo " Port ${WEB_ADMIN_PORT} still in use, stopping again..." echo " $ netstat -tlnp | grep ${WEB_ADMIN_PORT}"
systemctl stop easy-asterisk-webadmin 2>/dev/null || true netstat -tlnp 2>/dev/null | grep "${WEB_ADMIN_PORT}" || echo " (nothing found)"
# If still in use, stop again
if netstat -tlnp 2>/dev/null | grep -q ":${WEB_ADMIN_PORT}"; then
echo ""
echo " Port still in use, running stop again..."
echo " $ systemctl stop easy-asterisk-webadmin"
systemctl stop easy-asterisk-webadmin 2>&1 || true
sleep 1 sleep 1
echo ""
echo " $ netstat -tlnp | grep ${WEB_ADMIN_PORT}"
netstat -tlnp 2>/dev/null | grep "${WEB_ADMIN_PORT}" || echo " (nothing found)"
fi fi
# If still in use, kill directly # If STILL in use, kill processes
local port_pids=$(lsof -ti ":${WEB_ADMIN_PORT}" 2>/dev/null) if netstat -tlnp 2>/dev/null | grep -q ":${WEB_ADMIN_PORT}"; then
if [[ -n "$port_pids" ]]; then echo ""
echo " Killing remaining processes on port ${WEB_ADMIN_PORT}..." echo " Still in use, killing processes..."
echo "$port_pids" | xargs kill -9 2>/dev/null || true local pid=$(netstat -tlnp 2>/dev/null | grep ":${WEB_ADMIN_PORT}" | awk '{print $7}' | cut -d'/' -f1 | head -1)
if [[ -n "$pid" ]]; then
echo " $ kill -9 $pid"
kill -9 "$pid" 2>&1 || true
sleep 1 sleep 1
fi fi
fi
# Disable the service # Disable the service
systemctl disable easy-asterisk-webadmin 2>/dev/null || true systemctl disable easy-asterisk-webadmin 2>/dev/null || true
# Final verification # Final verification
if lsof -ti ":${WEB_ADMIN_PORT}" >/dev/null 2>&1; then echo ""
echo " Final check:"
echo " $ netstat -tlnp | grep ${WEB_ADMIN_PORT}"
if netstat -tlnp 2>/dev/null | grep ":${WEB_ADMIN_PORT}"; then
print_error "Port ${WEB_ADMIN_PORT} still in use!" print_error "Port ${WEB_ADMIN_PORT} still in use!"
lsof -i ":${WEB_ADMIN_PORT}" 2>/dev/null
else else
echo " (nothing found)"
print_success "Web Admin stopped" print_success "Web Admin stopped"
fi fi
;; ;;