version 1.3.3: выбор сложности пароля (лёгкий/сложный)
- app.py: gen_password(mode), api_users_create принимает password_mode - bot.py: меню выбора сложности перед созданием, gen_password(mode) - mita-ctl.sh: запрос сложности в menu_users, _create_user с pwd_mode - templates/index.html: селектор «Сложность пароля» в форме создания - README: обновлена дорожная карта
This commit is contained in:
@@ -72,7 +72,7 @@ curl -Ls https://raw.githubusercontent.com/grendervilll/mitactl/main/bootstrap.s
|
|||||||
- [x] Интеграция Cloudflare WARP через Docker
|
- [x] Интеграция Cloudflare WARP через Docker
|
||||||
- [x] Веб-панель с секретным путём (Flask + Gunicorn, path-based security)
|
- [x] Веб-панель с секретным путём (Flask + Gunicorn, path-based security)
|
||||||
- [x] Тёмная тема, адаптивный UI: Dashboard, Users, SSL, Security
|
- [x] Тёмная тема, адаптивный UI: Dashboard, Users, SSL, Security
|
||||||
- [x] CRUD пользователей (создание, удаление, список) — авто/ручное/bulk
|
- [x] CRUD пользователей (создание, удаление, список) — авто/ручное/bulk, выбор сложности пароля
|
||||||
- [x] Статистика трафика: день, 7 дней, 30 дней (позиционный парсинг mita get users)
|
- [x] Статистика трафика: день, 7 дней, 30 дней (позиционный парсинг mita get users)
|
||||||
- [x] Онлайн-статус пользователей
|
- [x] Онлайн-статус пользователей
|
||||||
- [x] Генерация клиентских конфигов (mieru JSON и sing-box JSON)
|
- [x] Генерация клиентских конфигов (mieru JSON и sing-box JSON)
|
||||||
|
|||||||
@@ -141,9 +141,10 @@ def mita_cmd(*args):
|
|||||||
r = subprocess.run(["mita", *args], capture_output=True, text=True)
|
r = subprocess.run(["mita", *args], capture_output=True, text=True)
|
||||||
return r.stdout.strip()
|
return r.stdout.strip()
|
||||||
|
|
||||||
def gen_password(length=64):
|
def gen_password(length=64, mode="hard"):
|
||||||
# Буквы и цифры только — спецсимволы могут ломать mita protobuf парсинг
|
if mode == "easy":
|
||||||
# Используем расширенный безопасный набор без проблемных символов
|
chars = string.ascii_letters + string.digits + "-._~*+"
|
||||||
|
else:
|
||||||
chars = string.ascii_letters + string.digits + "!@#%^*_-=+?."
|
chars = string.ascii_letters + string.digits + "!@#%^*_-=+?."
|
||||||
return "".join(secrets.choice(chars) for _ in range(length))
|
return "".join(secrets.choice(chars) for _ in range(length))
|
||||||
|
|
||||||
@@ -509,6 +510,7 @@ def api_users_create():
|
|||||||
count = int(data.get("count", 1))
|
count = int(data.get("count", 1))
|
||||||
mode = data.get("mode", "manual") # manual | auto
|
mode = data.get("mode", "manual") # manual | auto
|
||||||
names = data.get("names", []) # для manual
|
names = data.get("names", []) # для manual
|
||||||
|
pwd_mode = data.get("password_mode", "hard") # easy | hard
|
||||||
|
|
||||||
cfg = load_mita_config()
|
cfg = load_mita_config()
|
||||||
existing = {u["name"] for u in cfg.get("users", [])}
|
existing = {u["name"] for u in cfg.get("users", [])}
|
||||||
@@ -527,7 +529,7 @@ def api_users_create():
|
|||||||
if name in existing:
|
if name in existing:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
password = gen_password()
|
password = gen_password(mode=pwd_mode)
|
||||||
cfg.setdefault("users", []).append({"name": name, "password": password})
|
cfg.setdefault("users", []).append({"name": name, "password": password})
|
||||||
existing.add(name)
|
existing.add(name)
|
||||||
created.append({
|
created.append({
|
||||||
|
|||||||
@@ -82,7 +82,10 @@ def _mita_running():
|
|||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def gen_password(length=64):
|
def gen_password(length=64, mode="hard"):
|
||||||
|
if mode == "easy":
|
||||||
|
chars = string.ascii_letters + string.digits + "-._~*+"
|
||||||
|
else:
|
||||||
chars = string.ascii_letters + string.digits + "!@#%^*_-=+?."
|
chars = string.ascii_letters + string.digits + "!@#%^*_-=+?."
|
||||||
return "".join(secrets.choice(chars) for _ in range(length))
|
return "".join(secrets.choice(chars) for _ in range(length))
|
||||||
|
|
||||||
@@ -222,13 +225,28 @@ async def show_users(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
async def create_user(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def create_user(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
await query.answer()
|
await query.answer()
|
||||||
|
await query.edit_message_text(
|
||||||
|
"➕ *Создать пользователя*\n\nВыберите сложность пароля:",
|
||||||
|
reply_markup=InlineKeyboardMarkup([
|
||||||
|
[InlineKeyboardButton("🔓 Лёгкий (A-Z, 0-9, -._~*+)", callback_data="create_easy")],
|
||||||
|
[InlineKeyboardButton("🔐 Сложный (со спецсимволами)", callback_data="create_hard")],
|
||||||
|
[InlineKeyboardButton("« Назад", callback_data="main")],
|
||||||
|
]),
|
||||||
|
parse_mode="MarkdownV2"
|
||||||
|
)
|
||||||
|
|
||||||
|
@admin_only
|
||||||
|
async def create_exec(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
pwd_mode = "easy" if query.data == "create_easy" else "hard"
|
||||||
cfg = load_mita_config()
|
cfg = load_mita_config()
|
||||||
existing = {u["name"] for u in cfg.get("users", [])}
|
existing = {u["name"] for u in cfg.get("users", [])}
|
||||||
name = gen_username()
|
name = gen_username()
|
||||||
while name in existing:
|
while name in existing:
|
||||||
name = gen_username()
|
name = gen_username()
|
||||||
|
|
||||||
password = gen_password()
|
password = gen_password(mode=pwd_mode)
|
||||||
cfg.setdefault("users", []).append({"name": name, "password": password})
|
cfg.setdefault("users", []).append({"name": name, "password": password})
|
||||||
Path(MITA_CONFIG).write_text(json.dumps(cfg, indent=2, ensure_ascii=False))
|
Path(MITA_CONFIG).write_text(json.dumps(cfg, indent=2, ensure_ascii=False))
|
||||||
|
|
||||||
@@ -625,6 +643,8 @@ async def router(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
return await show_users(update, context)
|
return await show_users(update, context)
|
||||||
elif data == "create":
|
elif data == "create":
|
||||||
return await create_user(update, context)
|
return await create_user(update, context)
|
||||||
|
elif data in ("create_easy", "create_hard"):
|
||||||
|
return await create_exec(update, context)
|
||||||
elif data == "delete_menu":
|
elif data == "delete_menu":
|
||||||
return await delete_menu(update, context)
|
return await delete_menu(update, context)
|
||||||
elif data.startswith("delete_"):
|
elif data.startswith("delete_"):
|
||||||
|
|||||||
+19
-2
@@ -530,11 +530,23 @@ for u in users: print(f' - {u[\"name\"]}')
|
|||||||
echo ""
|
echo ""
|
||||||
read -r -p " Выбор: " choice
|
read -r -p " Выбор: " choice
|
||||||
|
|
||||||
|
# Запрашиваем сложность пароля один раз перед созданием
|
||||||
|
local pwd_mode="hard"
|
||||||
|
if [[ "$choice" == "1" || "$choice" == "2" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e " ${BOLD}Сложность пароля:${NC}"
|
||||||
|
echo -e " ${BOLD}1.${NC} Лёгкий — A-Z, a-z, 0-9, -._~*+ (без спецсимволов)"
|
||||||
|
echo -e " ${BOLD}2.${NC} Сложный — со спецсимволами (по умолчанию)"
|
||||||
|
echo ""
|
||||||
|
read -r -p " Выбор [2]: " pwd_choice
|
||||||
|
[[ "$pwd_choice" == "1" ]] && pwd_mode="easy"
|
||||||
|
fi
|
||||||
|
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1)
|
1)
|
||||||
read -r -p "Имя пользователя: " uname
|
read -r -p "Имя пользователя: " uname
|
||||||
[[ -z "$uname" ]] && { warn "Имя не указано"; pause; return; }
|
[[ -z "$uname" ]] && { warn "Имя не указано"; pause; return; }
|
||||||
_create_user "$uname"
|
_create_user "$uname" "$pwd_mode"
|
||||||
pause ;;
|
pause ;;
|
||||||
2)
|
2)
|
||||||
read -r -p "Количество пользователей: " cnt
|
read -r -p "Количество пользователей: " cnt
|
||||||
@@ -545,7 +557,7 @@ for u in users: print(f' - {u[\"name\"]}')
|
|||||||
noun=$(shuf -n1 -e fox hawk river storm ember peak orbit tide frost spark 2>/dev/null || echo "node")
|
noun=$(shuf -n1 -e fox hawk river storm ember peak orbit tide frost spark 2>/dev/null || echo "node")
|
||||||
rnd=$((RANDOM % 9000 + 1000))
|
rnd=$((RANDOM % 9000 + 1000))
|
||||||
uname="${adj}_${noun}_${rnd}"
|
uname="${adj}_${noun}_${rnd}"
|
||||||
_create_user "$uname"
|
_create_user "$uname" "$pwd_mode"
|
||||||
done
|
done
|
||||||
pause ;;
|
pause ;;
|
||||||
3)
|
3)
|
||||||
@@ -589,8 +601,13 @@ PYEOF
|
|||||||
|
|
||||||
_create_user() {
|
_create_user() {
|
||||||
local uname="$1"
|
local uname="$1"
|
||||||
|
local pwd_mode="$2"
|
||||||
local pass
|
local pass
|
||||||
|
if [[ "$pwd_mode" == "easy" ]]; then
|
||||||
|
pass=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9-._~*+' | head -c 32)
|
||||||
|
else
|
||||||
pass=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48)
|
pass=$(openssl rand -base64 48 | tr -d '/+=' | head -c 48)
|
||||||
|
fi
|
||||||
|
|
||||||
# Записываем пользователя в JSON и проверяем результат явно
|
# Записываем пользователя в JSON и проверяем результат явно
|
||||||
local py_result
|
local py_result
|
||||||
|
|||||||
+12
-4
@@ -95,6 +95,13 @@
|
|||||||
<label>Имя пользователя</label>
|
<label>Имя пользователя</label>
|
||||||
<input type="text" id="create-name" placeholder="например: vasya">
|
<input type="text" id="create-name" placeholder="например: vasya">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" style="max-width:160px">
|
||||||
|
<label>Сложность пароля</label>
|
||||||
|
<select id="create-pwd-mode">
|
||||||
|
<option value="hard">Сложный</option>
|
||||||
|
<option value="easy">Лёгкий</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-primary" onclick="createUsers()">
|
<button class="btn btn-primary" onclick="createUsers()">
|
||||||
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 4v16m8-8H4"/></svg>
|
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 4v16m8-8H4"/></svg>
|
||||||
@@ -490,17 +497,18 @@ toggleCreateMode();
|
|||||||
|
|
||||||
async function createUsers(){
|
async function createUsers(){
|
||||||
const mode=document.getElementById('create-mode').value;
|
const mode=document.getElementById('create-mode').value;
|
||||||
let body={};
|
const pwdMode=document.getElementById('create-pwd-mode').value;
|
||||||
|
let body={password_mode:pwdMode};
|
||||||
if(mode==='auto'){
|
if(mode==='auto'){
|
||||||
body={mode:'auto',count:parseInt(document.getElementById('create-count').value)||1};
|
body={...body,mode:'auto',count:parseInt(document.getElementById('create-count').value)||1};
|
||||||
}else if(mode==='manual'){
|
}else if(mode==='manual'){
|
||||||
const n=document.getElementById('create-name').value.trim();
|
const n=document.getElementById('create-name').value.trim();
|
||||||
if(!n){showToast('Введите имя',true);return;}
|
if(!n){showToast('Введите имя',true);return;}
|
||||||
body={mode:'manual',names:[n],count:1};
|
body={...body,mode:'manual',names:[n],count:1};
|
||||||
}else{
|
}else{
|
||||||
const lines=document.getElementById('bulk-names').value.split('\n').map(s=>s.trim()).filter(Boolean);
|
const lines=document.getElementById('bulk-names').value.split('\n').map(s=>s.trim()).filter(Boolean);
|
||||||
if(!lines.length){showToast('Введите имена',true);return;}
|
if(!lines.length){showToast('Введите имена',true);return;}
|
||||||
body={mode:'manual',names:lines,count:lines.length};
|
body={...body,mode:'manual',names:lines,count:lines.length};
|
||||||
}
|
}
|
||||||
const d=await api('/api/users/create','POST',body);
|
const d=await api('/api/users/create','POST',body);
|
||||||
if(!d||!d.created){showToast('Ошибка',true);return;}
|
if(!d||!d.created){showToast('Ошибка',true);return;}
|
||||||
|
|||||||
Reference in New Issue
Block a user