Merge pull request #254 from ReturnFI/beta
Bulk User Creation & Sorting Improvements
This commit is contained in:
14
core/cli.py
14
core/cli.py
@ -138,6 +138,20 @@ def add_user(username: str, traffic_limit: int, expiration_days: int, password:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
click.echo(f'{e}', err=True)
|
click.echo(f'{e}', err=True)
|
||||||
|
|
||||||
|
@cli.command('bulk-user-add')
|
||||||
|
@click.option('--traffic-gb', '-t', required=True, help='Traffic limit for each user in GB.', type=float)
|
||||||
|
@click.option('--expiration-days', '-e', required=True, help='Expiration duration for each user in days.', type=int)
|
||||||
|
@click.option('--count', '-c', required=True, help='Number of users to create.', type=int)
|
||||||
|
@click.option('--prefix', '-p', required=True, help='Prefix for usernames.', type=str)
|
||||||
|
@click.option('--start-number', '-s', default=1, help='Starting number for username suffix.', type=int)
|
||||||
|
@click.option('--unlimited', is_flag=True, default=False, help='Flag to mark users as unlimited (exempt from IP limits).')
|
||||||
|
def bulk_user_add(traffic_gb: float, expiration_days: int, count: int, prefix: str, start_number: int, unlimited: bool):
|
||||||
|
"""Adds multiple users in bulk."""
|
||||||
|
try:
|
||||||
|
cli_api.bulk_user_add(traffic_gb, expiration_days, count, prefix, start_number, unlimited)
|
||||||
|
click.echo(f"Successfully initiated the creation of {count} users with prefix '{prefix}'.")
|
||||||
|
except Exception as e:
|
||||||
|
click.echo(f'Error during bulk user addition: {e}', err=True)
|
||||||
|
|
||||||
@cli.command('edit-user')
|
@cli.command('edit-user')
|
||||||
@click.option('--username', '-u', required=True, help='Username for the user to edit', type=str)
|
@click.option('--username', '-u', required=True, help='Username for the user to edit', type=str)
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class Command(Enum):
|
|||||||
CHANGE_SNI_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_sni.py')
|
CHANGE_SNI_HYSTERIA2 = os.path.join(SCRIPT_DIR, 'hysteria2', 'change_sni.py')
|
||||||
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.py')
|
GET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'get_user.py')
|
||||||
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.py')
|
ADD_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'add_user.py')
|
||||||
|
BULK_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'bulk_users.py')
|
||||||
EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh')
|
EDIT_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'edit_user.sh')
|
||||||
RESET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'reset_user.py')
|
RESET_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'reset_user.py')
|
||||||
REMOVE_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'remove_user.py')
|
REMOVE_USER = os.path.join(SCRIPT_DIR, 'hysteria2', 'remove_user.py')
|
||||||
@ -285,6 +286,24 @@ def add_user(username: str, traffic_limit: int, expiration_days: int, password:
|
|||||||
|
|
||||||
run_cmd(command)
|
run_cmd(command)
|
||||||
|
|
||||||
|
def bulk_user_add(traffic_gb: float, expiration_days: int, count: int, prefix: str, start_number: int, unlimited: bool):
|
||||||
|
"""
|
||||||
|
Executes the bulk user creation script with specified parameters.
|
||||||
|
"""
|
||||||
|
command = [
|
||||||
|
'python3',
|
||||||
|
Command.BULK_USER.value,
|
||||||
|
'--traffic-gb', str(traffic_gb),
|
||||||
|
'--expiration-days', str(expiration_days),
|
||||||
|
'--count', str(count),
|
||||||
|
'--prefix', prefix,
|
||||||
|
'--start-number', str(start_number)
|
||||||
|
]
|
||||||
|
|
||||||
|
if unlimited:
|
||||||
|
command.append('--unlimited')
|
||||||
|
|
||||||
|
run_cmd(command)
|
||||||
|
|
||||||
def edit_user(username: str, new_username: str | None, new_traffic_limit: int | None, new_expiration_days: int | None, renew_password: bool, renew_creation_date: bool, blocked: bool | None, unlimited_ip: bool | None):
|
def edit_user(username: str, new_username: str | None, new_traffic_limit: int | None, new_expiration_days: int | None, renew_password: bool, renew_creation_date: bool, blocked: bool | None, unlimited_ip: bool | None):
|
||||||
'''
|
'''
|
||||||
|
|||||||
@ -60,8 +60,8 @@ def add_user(username, traffic_gb, expiration_days, password=None, creation_date
|
|||||||
print("Invalid date. Please provide a valid date in YYYY-MM-DD format.")
|
print("Invalid date. Please provide a valid date in YYYY-MM-DD format.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if not re.match(r"^[a-zA-Z0-9]+$", username):
|
if not re.match(r"^[a-zA-Z0-9_]+$", username):
|
||||||
print("Error: Username can only contain letters and numbers.")
|
print("Error: Username can only contain letters, numbers, and underscores.")
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
if not os.path.isfile(USERS_FILE):
|
if not os.path.isfile(USERS_FILE):
|
||||||
|
|||||||
111
core/scripts/hysteria2/bulk_users.py
Normal file
111
core/scripts/hysteria2/bulk_users.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import argparse
|
||||||
|
import re
|
||||||
|
from datetime import datetime
|
||||||
|
from init_paths import *
|
||||||
|
from paths import *
|
||||||
|
|
||||||
|
def add_bulk_users(traffic_gb, expiration_days, count, prefix, start_number, unlimited_user):
|
||||||
|
try:
|
||||||
|
traffic_bytes = int(float(traffic_gb) * 1073741824)
|
||||||
|
except ValueError:
|
||||||
|
print("Error: Traffic limit must be a numeric value.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if not os.path.isfile(USERS_FILE):
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'w') as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
except IOError:
|
||||||
|
print(f"Error: Could not create {USERS_FILE}.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(USERS_FILE, 'r+') as f:
|
||||||
|
try:
|
||||||
|
users_data = json.load(f)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Error: {USERS_FILE} contains invalid JSON.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
existing_users_lower = {u.lower() for u in users_data}
|
||||||
|
new_users_to_add = {}
|
||||||
|
creation_date = datetime.now().strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
try:
|
||||||
|
password_process = subprocess.run(['pwgen', '-s', '32', str(count)], capture_output=True, text=True, check=True)
|
||||||
|
passwords = password_process.stdout.strip().split('\n')
|
||||||
|
except (FileNotFoundError, subprocess.CalledProcessError):
|
||||||
|
print("Warning: 'pwgen' not found or failed. Falling back to UUID for password generation.")
|
||||||
|
passwords = [subprocess.check_output(['cat', '/proc/sys/kernel/random/uuid'], text=True).strip() for _ in range(count)]
|
||||||
|
|
||||||
|
if len(passwords) < count:
|
||||||
|
print("Error: Could not generate enough passwords.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
for i in range(count):
|
||||||
|
username = f"{prefix}{start_number + i}"
|
||||||
|
username_lower = username.lower()
|
||||||
|
|
||||||
|
if not re.match(r"^[a-zA-Z0-9_]+$", username_lower):
|
||||||
|
print(f"Error: Generated username '{username}' contains invalid characters. Use only letters, numbers, and underscores.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if username_lower in existing_users_lower or username_lower in new_users_to_add:
|
||||||
|
print(f"Warning: User '{username}' already exists. Skipping.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_users_to_add[username_lower] = {
|
||||||
|
"password": passwords[i],
|
||||||
|
"max_download_bytes": traffic_bytes,
|
||||||
|
"expiration_days": expiration_days,
|
||||||
|
"account_creation_date": creation_date,
|
||||||
|
"blocked": False,
|
||||||
|
"unlimited_user": unlimited_user
|
||||||
|
}
|
||||||
|
# print(f"Preparing to add user: {username}")
|
||||||
|
|
||||||
|
if not new_users_to_add:
|
||||||
|
print("No new users to add.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
users_data.update(new_users_to_add)
|
||||||
|
|
||||||
|
f.seek(0)
|
||||||
|
json.dump(users_data, f, indent=4)
|
||||||
|
f.truncate()
|
||||||
|
|
||||||
|
print(f"\nSuccessfully added {len(new_users_to_add)} users.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except IOError:
|
||||||
|
print(f"Error: Could not read or write to {USERS_FILE}.")
|
||||||
|
return 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred: {e}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser(description="Add bulk users to Hysteria2.")
|
||||||
|
parser.add_argument("-t", "--traffic-gb", dest="traffic_gb", type=float, required=True, help="Traffic limit for each user in GB.")
|
||||||
|
parser.add_argument("-e", "--expiration-days", dest="expiration_days", type=int, required=True, help="Expiration duration for each user in days.")
|
||||||
|
parser.add_argument("-c", "--count", type=int, required=True, help="Number of users to create.")
|
||||||
|
parser.add_argument("-p", "--prefix", type=str, required=True, help="Prefix for usernames.")
|
||||||
|
parser.add_argument("-s", "--start-number", type=int, default=1, help="Starting number for username suffix (default: 1).")
|
||||||
|
parser.add_argument("-u", "--unlimited", action='store_true', help="Flag to mark users as unlimited (exempt from IP limits).")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
sys.exit(add_bulk_users(
|
||||||
|
traffic_gb=args.traffic_gb,
|
||||||
|
expiration_days=args.expiration_days,
|
||||||
|
count=args.count,
|
||||||
|
prefix=args.prefix,
|
||||||
|
start_number=args.start_number,
|
||||||
|
unlimited_user=args.unlimited
|
||||||
|
))
|
||||||
@ -1,5 +1,6 @@
|
|||||||
|
import re
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from pydantic import BaseModel, RootModel, Field
|
from pydantic import BaseModel, RootModel, Field, field_validator
|
||||||
|
|
||||||
|
|
||||||
class UserInfoResponse(BaseModel):
|
class UserInfoResponse(BaseModel):
|
||||||
@ -26,6 +27,27 @@ class AddUserInputBody(BaseModel):
|
|||||||
creation_date: Optional[str] = None
|
creation_date: Optional[str] = None
|
||||||
unlimited: bool = False
|
unlimited: bool = False
|
||||||
|
|
||||||
|
@field_validator('username')
|
||||||
|
def validate_username(cls, v):
|
||||||
|
if not re.match(r"^[a-zA-Z0-9_]+$", v):
|
||||||
|
raise ValueError('Username can only contain letters, numbers, and underscores.')
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class AddBulkUsersInputBody(BaseModel):
|
||||||
|
traffic_gb: float
|
||||||
|
expiration_days: int
|
||||||
|
count: int
|
||||||
|
prefix: str
|
||||||
|
start_number: int = 1
|
||||||
|
unlimited: bool = False
|
||||||
|
|
||||||
|
@field_validator('prefix')
|
||||||
|
def validate_prefix(cls, v):
|
||||||
|
if not re.match(r"^[a-zA-Z0-9_]*$", v):
|
||||||
|
raise ValueError('Prefix can only contain letters, numbers, and underscores.')
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class EditUserInputBody(BaseModel):
|
class EditUserInputBody(BaseModel):
|
||||||
new_username: Optional[str] = None
|
new_username: Optional[str] = None
|
||||||
@ -36,6 +58,12 @@ class EditUserInputBody(BaseModel):
|
|||||||
blocked: Optional[bool] = None
|
blocked: Optional[bool] = None
|
||||||
unlimited_ip: Optional[bool] = None
|
unlimited_ip: Optional[bool] = None
|
||||||
|
|
||||||
|
@field_validator('new_username')
|
||||||
|
def validate_new_username(cls, v):
|
||||||
|
if v and not re.match(r"^[a-zA-Z0-9_]+$", v):
|
||||||
|
raise ValueError('Username can only contain letters, numbers, and underscores.')
|
||||||
|
return v
|
||||||
|
|
||||||
class NodeUri(BaseModel):
|
class NodeUri(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
uri: str
|
uri: str
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
from .schema.user import UserListResponse, UserInfoResponse, AddUserInputBody, EditUserInputBody, UserUriResponse
|
from .schema.user import UserListResponse, UserInfoResponse, AddUserInputBody, EditUserInputBody, UserUriResponse, AddBulkUsersInputBody
|
||||||
from .schema.response import DetailResponse
|
from .schema.response import DetailResponse
|
||||||
import cli_api
|
import cli_api
|
||||||
|
|
||||||
@ -57,6 +57,29 @@ async def add_user_api(body: AddUserInputBody):
|
|||||||
detail=f"An unexpected error occurred while adding user '{body.username}': {str(e)}")
|
detail=f"An unexpected error occurred while adding user '{body.username}': {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/bulk/', response_model=DetailResponse, status_code=201)
|
||||||
|
async def add_bulk_users_api(body: AddBulkUsersInputBody):
|
||||||
|
"""
|
||||||
|
Add multiple users in bulk.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
cli_api.bulk_user_add(
|
||||||
|
traffic_gb=body.traffic_gb,
|
||||||
|
expiration_days=body.expiration_days,
|
||||||
|
count=body.count,
|
||||||
|
prefix=body.prefix,
|
||||||
|
start_number=body.start_number,
|
||||||
|
unlimited=body.unlimited
|
||||||
|
)
|
||||||
|
return DetailResponse(detail=f"Successfully started adding {body.count} users with prefix '{body.prefix}'.")
|
||||||
|
except cli_api.CommandExecutionError as e:
|
||||||
|
raise HTTPException(status_code=400,
|
||||||
|
detail=f'Failed to add bulk users: {str(e)}')
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500,
|
||||||
|
detail=f"An unexpected error occurred while adding bulk users: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
@router.get('/{username}', response_model=UserInfoResponse)
|
@router.get('/{username}', response_model=UserInfoResponse)
|
||||||
async def get_user_api(username: str):
|
async def get_user_api(username: str):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -160,8 +160,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- Add User Modal -->
|
<!-- Add User Modal -->
|
||||||
<div class="modal fade" id="addUserModal" tabindex="-1" role="dialog" aria-labelledby="addUserModalLabel"
|
<div class="modal fade" id="addUserModal" tabindex="-1" role="dialog" aria-labelledby="addUserModalLabel" aria-hidden="true">
|
||||||
aria-hidden="true">
|
|
||||||
<div class="modal-dialog" role="document">
|
<div class="modal-dialog" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@ -171,7 +170,17 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="addUserForm">
|
<ul class="nav nav-tabs" id="addUserTab" role="tablist">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" id="single-user-tab" data-toggle="tab" href="#single-user" role="tab" aria-controls="single-user" aria-selected="true">Add User</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="bulk-users-tab" data-toggle="tab" href="#bulk-users" role="tab" aria-controls="bulk-users" aria-selected="false">Bulk Add</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="addUserTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="single-user" role="tabpanel" aria-labelledby="single-user-tab">
|
||||||
|
<form id="addUserForm" class="mt-3">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="addUsername">Username</label>
|
<label for="addUsername">Username</label>
|
||||||
<input type="text" class="form-control" id="addUsername" name="username" required>
|
<input type="text" class="form-control" id="addUsername" name="username" required>
|
||||||
@ -183,8 +192,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="addExpirationDays">Expiration Days</label>
|
<label for="addExpirationDays">Expiration Days</label>
|
||||||
<input type="number" class="form-control" id="addExpirationDays" name="expiration_days"
|
<input type="number" class="form-control" id="addExpirationDays" name="expiration_days" required>
|
||||||
required>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check mb-3 requires-iplimit-service" style="display: none;">
|
<div class="form-check mb-3 requires-iplimit-service" style="display: none;">
|
||||||
<input type="checkbox" class="form-check-input" id="addUnlimited" name="unlimited">
|
<input type="checkbox" class="form-check-input" id="addUnlimited" name="unlimited">
|
||||||
@ -193,9 +201,46 @@
|
|||||||
<button type="submit" class="btn btn-primary" id="addSubmitButton">Add User</button>
|
<button type="submit" class="btn btn-primary" id="addSubmitButton">Add User</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tab-pane fade" id="bulk-users" role="tabpanel" aria-labelledby="bulk-users-tab">
|
||||||
|
<form id="addBulkUsersForm" class="mt-3">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="addBulkPrefix">Username Prefix</label>
|
||||||
|
<input type="text" class="form-control" id="addBulkPrefix" name="prefix" required>
|
||||||
|
<small class="form-text text-danger" id="addBulkPrefixError"></small>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="addBulkCount">Number of Users</label>
|
||||||
|
<input type="number" class="form-control" id="addBulkCount" name="count" value="10" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="addBulkStartNumber">Start Number</label>
|
||||||
|
<input type="number" class="form-control" id="addBulkStartNumber" name="start_number" value="1" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="addBulkTrafficLimit">Traffic Limit (GB)</label>
|
||||||
|
<input type="number" class="form-control" id="addBulkTrafficLimit" name="traffic_gb" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col-md-6">
|
||||||
|
<label for="addBulkExpirationDays">Expiration Days</label>
|
||||||
|
<input type="number" class="form-control" id="addBulkExpirationDays" name="expiration_days" required>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-check mb-3 requires-iplimit-service" style="display: none;">
|
||||||
|
<input type="checkbox" class="form-check-input" id="addBulkUnlimited" name="unlimited">
|
||||||
|
<label class="form-check-label" for="addBulkUnlimited">Unlimited IP (Exempt from IP limit checks)</label>
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" id="addBulkSubmitButton">Add Bulk Users</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Edit User Modal -->
|
<!-- Edit User Modal -->
|
||||||
<div class="modal fade" id="editUserModal" tabindex="-1" role="dialog" aria-labelledby="editUserModalLabel"
|
<div class="modal fade" id="editUserModal" tabindex="-1" role="dialog" aria-labelledby="editUserModalLabel"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
@ -277,14 +322,18 @@
|
|||||||
|
|
||||||
checkIpLimitServiceStatus();
|
checkIpLimitServiceStatus();
|
||||||
|
|
||||||
const usernameRegex = /^[a-zA-Z0-9]+$/;
|
const usernameRegex = /^[a-zA-Z0-9_]+$/;
|
||||||
|
|
||||||
function validateUsername(username, errorElementId) {
|
function validateUsername(username, errorElementId) {
|
||||||
const isValid = usernameRegex.test(username);
|
|
||||||
const errorElement = $("#" + errorElementId);
|
const errorElement = $("#" + errorElementId);
|
||||||
|
if (!username) {
|
||||||
|
errorElement.text("");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const isValid = usernameRegex.test(username);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
errorElement.text("Usernames can only contain letters and numbers.");
|
errorElement.text("Usernames can only contain letters, numbers, and underscores.");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
errorElement.text("");
|
errorElement.text("");
|
||||||
@ -297,14 +346,18 @@
|
|||||||
$("#addUsername").on("input", function () {
|
$("#addUsername").on("input", function () {
|
||||||
const username = $(this).val();
|
const username = $(this).val();
|
||||||
const isValid = validateUsername(username, "addUsernameError");
|
const isValid = validateUsername(username, "addUsernameError");
|
||||||
$("#addUserForm button[type='submit']").prop("disabled", !isValid);
|
|
||||||
$("#addSubmitButton").prop("disabled", !isValid);
|
$("#addSubmitButton").prop("disabled", !isValid);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#addBulkPrefix").on("input", function () {
|
||||||
|
const prefix = $(this).val();
|
||||||
|
const isValid = validateUsername(prefix, "addBulkPrefixError");
|
||||||
|
$("#addBulkSubmitButton").prop("disabled", !isValid);
|
||||||
|
});
|
||||||
|
|
||||||
$("#editUsername").on("input", function () {
|
$("#editUsername").on("input", function () {
|
||||||
const username = $(this).val();
|
const username = $(this).val();
|
||||||
const isValid = validateUsername(username, "editUsernameError");
|
const isValid = validateUsername(username, "editUsernameError");
|
||||||
$("#editUserForm button[type='submit']").prop("disabled", !isValid);
|
|
||||||
$("#editSubmitButton").prop("disabled", !isValid);
|
$("#editSubmitButton").prop("disabled", !isValid);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -466,6 +519,54 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("#addBulkUsersForm").on("submit", function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!validateUsername($("#addBulkPrefix").val(), "addBulkPrefixError")) {
|
||||||
|
$("#addBulkSubmitButton").prop("disabled", true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$("#addBulkSubmitButton").prop("disabled", true);
|
||||||
|
|
||||||
|
const jsonData = {
|
||||||
|
prefix: $("#addBulkPrefix").val(),
|
||||||
|
count: parseInt($("#addBulkCount").val()),
|
||||||
|
start_number: parseInt($("#addBulkStartNumber").val()),
|
||||||
|
traffic_gb: parseFloat($("#addBulkTrafficLimit").val()),
|
||||||
|
expiration_days: parseInt($("#addBulkExpirationDays").val()),
|
||||||
|
unlimited: $("#addBulkUnlimited").is(":checked")
|
||||||
|
};
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: "{{ url_for('add_bulk_users_api') }}",
|
||||||
|
method: "POST",
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify(jsonData),
|
||||||
|
success: function(response) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Success!",
|
||||||
|
text: response.detail || "Bulk user creation started successfully!",
|
||||||
|
icon: "success",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
}).then(() => {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: function(jqXHR) {
|
||||||
|
let errorMessage = "An error occurred during bulk user creation.";
|
||||||
|
if (jqXHR.responseJSON && jqXHR.responseJSON.detail) {
|
||||||
|
errorMessage = jqXHR.responseJSON.detail;
|
||||||
|
}
|
||||||
|
Swal.fire({
|
||||||
|
title: "Error!",
|
||||||
|
text: errorMessage,
|
||||||
|
icon: "error",
|
||||||
|
confirmButtonText: "OK",
|
||||||
|
});
|
||||||
|
$("#addBulkSubmitButton").prop("disabled", false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
$(document).on("click", ".edit-user", function () {
|
$(document).on("click", ".edit-user", function () {
|
||||||
const username = $(this).data("user");
|
const username = $(this).data("user");
|
||||||
const row = $(this).closest("tr");
|
const row = $(this).closest("tr");
|
||||||
@ -795,10 +896,17 @@
|
|||||||
|
|
||||||
$('#addUserModal').on('show.bs.modal', function (event) {
|
$('#addUserModal').on('show.bs.modal', function (event) {
|
||||||
$('#addUserForm')[0].reset();
|
$('#addUserForm')[0].reset();
|
||||||
|
$('#addBulkUsersForm')[0].reset();
|
||||||
$('#addUsernameError').text('');
|
$('#addUsernameError').text('');
|
||||||
|
$('#addBulkPrefixError').text('');
|
||||||
$('#addTrafficLimit').val('30');
|
$('#addTrafficLimit').val('30');
|
||||||
$('#addExpirationDays').val('30');
|
$('#addExpirationDays').val('30');
|
||||||
|
$('#addBulkTrafficLimit').val('30');
|
||||||
|
$('#addBulkExpirationDays').val('30');
|
||||||
|
$('#addBulkStartNumber').val('1');
|
||||||
$('#addSubmitButton').prop('disabled', true);
|
$('#addSubmitButton').prop('disabled', true);
|
||||||
|
$('#addBulkSubmitButton').prop('disabled', true);
|
||||||
|
$('#addUserModal a[data-toggle="tab"]').first().tab('show');
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#searchButton").on("click", filterUsers);
|
$("#searchButton").on("click", filterUsers);
|
||||||
|
|||||||
Reference in New Issue
Block a user