This site is developed to XHTML and CSS2 W3C standards.
If you see this paragraph, your browser does not support those standards and you
need to upgrade. Visit WaSP
for a variety of options.
Paste #726
Posted by: web
Posted on: 2026-09-17 19:13:38
Age: 3 hrs ago
Views: 5
import os
import re
import subprocess
from bottle import run, route, request, redirect, template
# Простая HTML-страница со встроенными стилями
HTML_TEMPLATE = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>NNTP (INN2) Web Panel</title>
<style>
body { font-family: sans-serif; max-width: 700px; margin: 40px auto; padding: 0 20px; background: #f4f6f9; color: #333; }
.box { background: white; padding: 20px; border-radius: 6px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); margin-bottom: 20px; }
h2 { margin-top: 0; color: #2c3e50; border-bottom: 2px solid #eee; padding-bottom: 10px; }
input[type="text"] { width: 70%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; font-size: 16px; }
button { padding: 10px 20px; background: #3498db; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background: #2980b9; }
button.del { background: #e74c3c; padding: 5px 10px; font-size: 14px; }
button.del:hover { background: #c0392b; }
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #eee; }
th { background: #f8f9fa; color: #555; }
.error { color: #721c24; background: #f8d7da; padding: 10px; border-radius: 4px; margin-bottom: 15px; }
.success { color: #155724; background: #d4edda; padding: 10px; border-radius: 4px; margin-bottom: 15px; }
</style>
</head>
<body>
<h1>NNTP (INN2) Управление Сервером</h1>
% if msg:
<div class="success">{{msg}}</div>
% end
% if err:
<div class="error">{{err}}</div>
% end
<div class="box">
<h2>Создать новую группу / подгруппу</h2>
<form action="/create" method="post">
<input type="text" name="group_name" placeholder="например: local.chat.forum" required>
<button type="submit">Создать</button>
</form>
</div>
<div class="box">
<h2>Активные группы на сервере</h2>
<table>
<tr>
<th>Имя группы</th>
<th>Статус</th>
<th>Действие</th>
</tr>
% for group, status in groups:
<tr>
<td><strong>{{group}}</strong></td>
<td>{{status}} (открыта)</td>
<td>
% if group not in ['control', 'junk']:
<form action="/delete" method="post" style="display:inline;" onsubmit="return confirm('Удалить группу {{group}}?');">
<input type="hidden" name="group_name" value="{{group}}">
<button type="submit" class="del">Удалить</button>
</form>
% else:
<span style="color:#aaa;">системная</span>
% end
</td>
</tr>
% end
</table>
</div>
</body>
</html>
"""
def run_nntp_cmd(action, group_name=""):
"""Прямой и безопасный вызов ctlinnd через системный шелл, исключающий ошибки парсинга аргументов"""
if action == "list":
cmd = "sudo -u news ctlinnd list active"
elif action == "create":
cmd = f"sudo -u news ctlinnd newgroup {group_name} y"
elif action == "delete":
cmd = f"sudo -u news ctlinnd rmgroup {group_name}"
else:
return False, "Неверное действие"
res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if res.returncode == 0:
return True, res.stdout
else:
return False, res.stderr if res.stderr else res.stdout
def get_active_groups():
success, output = run_nntp_cmd("list")
if not success:
return []
parsed_groups = []
for line in output.strip().split("\n"):
if not line: continue
parts = line.split()
if len(parts) >= 4:
parsed_groups.append((parts[0], parts[3]))
return parsed_groups
@route('/')
def index():
err = request.query.get('err', '')
msg = request.query.get('msg', '')
return template(HTML_TEMPLATE, groups=get_active_groups(), err=err, msg=msg)
@route('/create', method='POST')
def create():
group_name = request.forms.get('group_name', '').strip()
if not group_name or not re.match(r"^[a-zA-Z0-9\.-]+$", group_name):
return redirect('/?err=Invalid+group+name')
success, out = run_nntp_cmd("create", group_name)
if success:
return redirect(f'/?msg=Group+{group_name}+created+successfully')
else:
return redirect(f'/?err={out.replace(" ", "+")}')
@route('/delete', method='POST')
def delete():
group_name = request.forms.get('group_name', '').strip()
if group_name in ['control', 'junk']:
return redirect('/?err=Cannot+delete+system+groups')
success, out = run_nntp_cmd("delete", group_name)
if success:
return redirect(f'/?msg=Group+{group_name}+deleted')
else:
return redirect(f'/?err={out.replace(" ", "+")}')
# Запуск веб-сервера на порту 8080 для всех IP адресов
if __name__ == '__main__':
run(host='0.0.0.0', port=8080, debug=True)
Download raw |
Create new paste