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:
+195
-18
@@ -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
|
||||||
;;
|
;;
|
||||||
|
|||||||
Reference in New Issue
Block a user