mirror of
https://github.com/PRVTPRO/Amnezia-Web-Panel.git
synced 2026-10-23 03:37:36 +09:00
Improved Telemt
1) Improved Telemt request processing. 2) Telemt API support.
This commit is contained in:
1 parent
8353f3cad7
commit
082c4f398a
6 files changed
+278
-39
No files matched your search
+16
@@ -0,0 +1,16 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM python:3.14-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Копируем requirements.txt и устанавливаем зависимости
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip3 install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Копируем остальные файлы проекта
|
||||
COPY . .
|
||||
|
||||
|
||||
# Команда запуска приложения
|
||||
CMD ["python3", "app.py"]
|
||||
@@ -47,7 +47,7 @@ else:
|
||||
application_path = os.path.dirname(__file__)
|
||||
|
||||
DATA_FILE = os.path.join(application_path, 'data.json')
|
||||
CURRENT_VERSION = "v1.4.0"
|
||||
CURRENT_VERSION = "v1.4.1"
|
||||
|
||||
|
||||
# ======================== Translations ========================
|
||||
@@ -505,6 +505,9 @@ class AddConnectionRequest(BaseModel):
|
||||
telemt_quota: Optional[str] = None
|
||||
telemt_max_ips: Optional[int] = None
|
||||
telemt_expiry: Optional[str] = None
|
||||
telemt_secret: Optional[str] = None
|
||||
telemt_ad_tag: Optional[str] = None
|
||||
telemt_max_conns: Optional[int] = None
|
||||
|
||||
|
||||
class EditConnectionRequest(BaseModel):
|
||||
@@ -513,6 +516,9 @@ class EditConnectionRequest(BaseModel):
|
||||
telemt_quota: Optional[str] = None
|
||||
telemt_max_ips: Optional[int] = None
|
||||
telemt_expiry: Optional[str] = None
|
||||
telemt_secret: Optional[str] = None
|
||||
telemt_ad_tag: Optional[str] = None
|
||||
telemt_max_conns: Optional[int] = None
|
||||
|
||||
|
||||
class ConnectionActionRequest(BaseModel):
|
||||
@@ -539,6 +545,12 @@ class AddUserRequest(BaseModel):
|
||||
protocol: Optional[str] = None
|
||||
connection_name: Optional[str] = None
|
||||
expiration_date: Optional[str] = None
|
||||
telemt_quota: Optional[str] = None
|
||||
telemt_max_ips: Optional[int] = None
|
||||
telemt_expiry: Optional[str] = None
|
||||
telemt_secret: Optional[str] = None
|
||||
telemt_ad_tag: Optional[str] = None
|
||||
telemt_max_conns: Optional[int] = None
|
||||
|
||||
|
||||
|
||||
@@ -613,6 +625,9 @@ class AddUserConnectionRequest(BaseModel):
|
||||
telemt_quota: Optional[str] = None
|
||||
telemt_max_ips: Optional[int] = None
|
||||
telemt_expiry: Optional[str] = None
|
||||
telemt_secret: Optional[str] = None
|
||||
telemt_ad_tag: Optional[str] = None
|
||||
telemt_max_conns: Optional[int] = None
|
||||
|
||||
|
||||
class ShareSetupRequest(BaseModel):
|
||||
@@ -1443,7 +1458,10 @@ async def api_add_connection(request: Request, server_id: int, req: AddConnectio
|
||||
req.protocol, req.name, server['host'], port,
|
||||
telemt_quota=req.telemt_quota,
|
||||
telemt_max_ips=req.telemt_max_ips,
|
||||
telemt_expiry=req.telemt_expiry
|
||||
telemt_expiry=req.telemt_expiry,
|
||||
secret=req.telemt_secret,
|
||||
user_ad_tag=req.telemt_ad_tag,
|
||||
max_tcp_conns=req.telemt_max_conns
|
||||
)
|
||||
elif req.protocol == 'wireguard':
|
||||
result = manager.add_client(req.name, server['host'])
|
||||
@@ -1521,6 +1539,9 @@ async def api_edit_connection(request: Request, server_id: int, req: EditConnect
|
||||
edit_params['telemt_quota'] = req.telemt_quota
|
||||
edit_params['telemt_max_ips'] = req.telemt_max_ips
|
||||
edit_params['telemt_expiry'] = req.telemt_expiry
|
||||
edit_params['secret'] = req.telemt_secret
|
||||
edit_params['user_ad_tag'] = req.telemt_ad_tag
|
||||
edit_params['max_tcp_conns'] = req.telemt_max_conns
|
||||
|
||||
result = manager.edit_client(req.protocol, req.client_id, edit_params)
|
||||
ssh.disconnect()
|
||||
@@ -1695,7 +1716,18 @@ async def api_add_user(request: Request, req: AddUserRequest):
|
||||
ssh = get_ssh(server)
|
||||
ssh.connect()
|
||||
manager = get_protocol_manager(ssh, req.protocol)
|
||||
conn_result = manager.add_client(req.protocol, conn_name, server['host'], port)
|
||||
if req.protocol == 'telemt':
|
||||
conn_result = manager.add_client(
|
||||
req.protocol, conn_name, server['host'], port,
|
||||
telemt_quota=req.telemt_quota,
|
||||
telemt_max_ips=req.telemt_max_ips,
|
||||
telemt_expiry=req.telemt_expiry,
|
||||
secret=req.telemt_secret,
|
||||
user_ad_tag=req.telemt_ad_tag,
|
||||
max_tcp_conns=req.telemt_max_conns
|
||||
)
|
||||
else:
|
||||
conn_result = manager.add_client(req.protocol, conn_name, server['host'], port)
|
||||
ssh.disconnect()
|
||||
|
||||
if conn_result.get('client_id'):
|
||||
@@ -1825,7 +1857,18 @@ async def api_add_user_connection(request: Request, user_id: str, req: AddUserCo
|
||||
result = {'client_id': target_client_id, 'config': config}
|
||||
else:
|
||||
# Create new client
|
||||
result = await asyncio.to_thread(manager.add_client, req.protocol, req.name, server['host'], port)
|
||||
if req.protocol == 'telemt':
|
||||
result = await asyncio.to_thread(
|
||||
manager.add_client, req.protocol, req.name, server['host'], port,
|
||||
telemt_quota=req.telemt_quota,
|
||||
telemt_max_ips=req.telemt_max_ips,
|
||||
telemt_expiry=req.telemt_expiry,
|
||||
secret=req.telemt_secret,
|
||||
user_ad_tag=req.telemt_ad_tag,
|
||||
max_tcp_conns=req.telemt_max_conns
|
||||
)
|
||||
else:
|
||||
result = await asyncio.to_thread(manager.add_client, req.protocol, req.name, server['host'], port)
|
||||
|
||||
await asyncio.to_thread(ssh.disconnect)
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ services:
|
||||
- "127.0.0.1:9090:9090"
|
||||
- "127.0.0.1:9091:9091"
|
||||
# Allow caching 'proxy-secret' in read-only container
|
||||
working_dir: /run/telemt
|
||||
working_dir: /app/conf
|
||||
volumes:
|
||||
- ./config.toml:/run/telemt/config.toml:ro
|
||||
- .:/app/conf:rw
|
||||
tmpfs:
|
||||
- /run/telemt:rw,mode=1777,size=1m
|
||||
environment:
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE # allow binding to port 443
|
||||
read_only: true
|
||||
read_only: false
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
ulimits:
|
||||
|
||||
+109
-31
@@ -125,9 +125,9 @@ class TelemtManager:
|
||||
self.ssh.upload_file_sudo(dockerfile, f"{remote_dir}/Dockerfile")
|
||||
|
||||
results.append("Starting Telemt container...")
|
||||
out, err, code = self.ssh.run_sudo_command(f"cd {remote_dir} && docker compose up -d --build", timeout=600)
|
||||
out, err, code = self.ssh.run_sudo_command(f"sh -c 'cd {remote_dir} && docker compose up -d --build'", timeout=600)
|
||||
if code != 0:
|
||||
self.ssh.run_sudo_command(f"cd {remote_dir} && docker-compose up -d --build", timeout=600)
|
||||
self.ssh.run_sudo_command(f"sh -c 'cd {remote_dir} && docker-compose up -d --build'", timeout=600)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
@@ -246,7 +246,7 @@ class TelemtManager:
|
||||
users[full_name] = secret
|
||||
return users
|
||||
|
||||
def add_client(self, protocol_type, name, host='', port='', telemt_quota=None, telemt_max_ips=None, telemt_expiry=None):
|
||||
def add_client(self, protocol_type, name, host='', port='', **kwargs):
|
||||
username = re.sub(r'[^a-zA-Z0-9_.-]', '', name.replace(' ', '_'))
|
||||
if not username: username = "user_" + uuid.uuid4().hex[:8]
|
||||
|
||||
@@ -258,19 +258,54 @@ class TelemtManager:
|
||||
username = f"{base_username}_{idx}"
|
||||
idx += 1
|
||||
|
||||
secret = secrets.token_hex(16)
|
||||
secret = kwargs.get('secret') or secrets.token_hex(16)
|
||||
|
||||
# 1. Update config file for persistence (but don't restart yet)
|
||||
config_text = self._insert_into_section(config_text, "access.users", f'{username} = "{secret}"')
|
||||
if telemt_quota:
|
||||
config_text = self._insert_into_section(config_text, "access.user_data_quota", f'{username} = {telemt_quota}')
|
||||
if telemt_max_ips:
|
||||
config_text = self._insert_into_section(config_text, "access.user_max_unique_ips", f'{username} = {telemt_max_ips}')
|
||||
if telemt_expiry:
|
||||
config_text = self._insert_into_section(config_text, "access.user_expirations", f'{username} = "{telemt_expiry}"')
|
||||
|
||||
api_payload = {
|
||||
"username": username,
|
||||
"secret": secret
|
||||
}
|
||||
|
||||
if kwargs.get('telemt_quota'):
|
||||
val = int(kwargs['telemt_quota'])
|
||||
config_text = self._insert_into_section(config_text, "access.user_data_quota", f'{username} = {val}')
|
||||
api_payload['data_quota_bytes'] = val
|
||||
|
||||
if kwargs.get('telemt_max_ips'):
|
||||
val = int(kwargs['telemt_max_ips'])
|
||||
config_text = self._insert_into_section(config_text, "access.user_max_unique_ips", f'{username} = {val}')
|
||||
api_payload['max_unique_ips'] = val
|
||||
|
||||
if kwargs.get('telemt_expiry'):
|
||||
val = kwargs['telemt_expiry']
|
||||
config_text = self._insert_into_section(config_text, "access.user_expirations", f'{username} = "{val}"')
|
||||
api_payload['expiration_rfc3339'] = val
|
||||
|
||||
self.save_server_config(protocol_type, config_text)
|
||||
if kwargs.get('user_ad_tag'):
|
||||
val = kwargs['user_ad_tag']
|
||||
config_text = self._insert_into_section(config_text, "access.user_ad_tags", f'{username} = "{val}"')
|
||||
api_payload['user_ad_tag'] = val
|
||||
|
||||
if kwargs.get('max_tcp_conns'):
|
||||
val = int(kwargs['max_tcp_conns'])
|
||||
config_text = self._insert_into_section(config_text, "access.user_max_tcp_conns", f'{username} = {val}')
|
||||
api_payload['max_tcp_conns'] = val
|
||||
|
||||
# Save config to host
|
||||
self.ssh.upload_file_sudo(config_text.replace('\r\n', '\n'), "/opt/amnezia/telemt/config.toml")
|
||||
|
||||
# 2. Call API for immediate effect
|
||||
resp = self._api_request("POST", "/v1/users", data=api_payload)
|
||||
|
||||
link = f"tg://proxy?server={host}&port={port}&secret={secret}"
|
||||
# If API returned specialized links, use them
|
||||
if resp and resp.get('ok') and resp.get('data', {}).get('links'):
|
||||
links = resp['data']['links']
|
||||
if links.get('tls'): link = links['tls'][0]
|
||||
elif links.get('secure'): link = links['secure'][0]
|
||||
|
||||
return {
|
||||
"client_id": username,
|
||||
"config": link,
|
||||
@@ -278,19 +313,48 @@ class TelemtManager:
|
||||
}
|
||||
|
||||
def edit_client(self, protocol_type, client_id, new_params):
|
||||
"""Update existing client parameters in config."""
|
||||
"""Update existing client parameters via API and in config."""
|
||||
config_text = self._get_server_config()
|
||||
api_payload = {}
|
||||
|
||||
if 'telemt_quota' in new_params:
|
||||
config_text = self._update_line_in_section(config_text, "access.user_data_quota", client_id, new_params['telemt_quota'])
|
||||
if 'telemt_max_ips' in new_params:
|
||||
config_text = self._update_line_in_section(config_text, "access.user_max_unique_ips", client_id, new_params['telemt_max_ips'])
|
||||
if 'telemt_expiry' in new_params:
|
||||
# Expiry is a string with quotes
|
||||
val = f'"{new_params["telemt_expiry"]}"' if new_params['telemt_expiry'] else None
|
||||
config_text = self._update_line_in_section(config_text, "access.user_expirations", client_id, val)
|
||||
val = int(new_params['telemt_quota']) if new_params['telemt_quota'] else None
|
||||
config_text = self._update_line_in_section(config_text, "access.user_data_quota", client_id, val)
|
||||
api_payload['data_quota_bytes'] = val
|
||||
|
||||
self.save_server_config(protocol_type, config_text)
|
||||
if 'telemt_max_ips' in new_params:
|
||||
val = int(new_params['telemt_max_ips']) if new_params['telemt_max_ips'] else None
|
||||
config_text = self._update_line_in_section(config_text, "access.user_max_unique_ips", client_id, val)
|
||||
api_payload['max_unique_ips'] = val
|
||||
|
||||
if 'telemt_expiry' in new_params:
|
||||
val = new_params['telemt_expiry']
|
||||
quoted_val = f'"{val}"' if val else None
|
||||
config_text = self._update_line_in_section(config_text, "access.user_expirations", client_id, quoted_val)
|
||||
api_payload['expiration_rfc3339'] = val
|
||||
|
||||
if 'secret' in new_params:
|
||||
val = new_params['secret']
|
||||
quoted_val = f'"{val}"' if val else None
|
||||
config_text = self._update_line_in_section(config_text, "access.users", client_id, quoted_val)
|
||||
api_payload['secret'] = val
|
||||
|
||||
if 'user_ad_tag' in new_params:
|
||||
val = new_params['user_ad_tag']
|
||||
quoted_val = f'"{val}"' if val else None
|
||||
config_text = self._update_line_in_section(config_text, "access.user_ad_tags", client_id, quoted_val)
|
||||
api_payload['user_ad_tag'] = val
|
||||
|
||||
if 'max_tcp_conns' in new_params:
|
||||
val = int(new_params['max_tcp_conns']) if new_params['max_tcp_conns'] else None
|
||||
config_text = self._update_line_in_section(config_text, "access.user_max_tcp_conns", client_id, val)
|
||||
api_payload['max_tcp_conns'] = val
|
||||
|
||||
# Save config to host
|
||||
self.ssh.upload_file_sudo(config_text.replace('\r\n', '\n'), "/opt/amnezia/telemt/config.toml")
|
||||
|
||||
# API call
|
||||
self._api_request("PATCH", f"/v1/users/{client_id}", data=api_payload)
|
||||
return {"status": "success"}
|
||||
|
||||
def _update_line_in_section(self, config_text, section_name, client_id, value):
|
||||
@@ -306,23 +370,18 @@ class TelemtManager:
|
||||
|
||||
if section_end == -1: section_end = len(lines)
|
||||
if section_start == -1:
|
||||
# Section not found, add it
|
||||
if value is not None:
|
||||
lines.append(f"[{section_name}]")
|
||||
lines.append(f'{client_id} = {value}')
|
||||
lines.append("")
|
||||
return '\n'.join(lines)
|
||||
|
||||
# Look for client in section
|
||||
found = False
|
||||
for i in range(section_start + 1, section_end):
|
||||
line = lines[i].strip().lstrip('#').strip()
|
||||
if line.startswith(f"{client_id} ") or line.startswith(f"{client_id}="):
|
||||
if value is None:
|
||||
# Remove line if value is None
|
||||
lines.pop(i)
|
||||
else:
|
||||
lines[i] = f'{client_id} = {value}'
|
||||
if value is None: lines.pop(i)
|
||||
else: lines[i] = f'{client_id} = {value}'
|
||||
found = True
|
||||
break
|
||||
|
||||
@@ -347,6 +406,10 @@ class TelemtManager:
|
||||
return '\n'.join(lines)
|
||||
|
||||
def remove_client(self, protocol_type, client_id):
|
||||
# 1. API
|
||||
self._api_request("DELETE", f"/v1/users/{client_id}")
|
||||
|
||||
# 2. Config
|
||||
config_text = self._get_server_config()
|
||||
lines = config_text.split('\n')
|
||||
new_lines = []
|
||||
@@ -355,9 +418,13 @@ class TelemtManager:
|
||||
if stripped.startswith(f"{client_id} ") or stripped.startswith(f"{client_id}="):
|
||||
continue
|
||||
new_lines.append(line)
|
||||
self.save_server_config(protocol_type, '\n'.join(new_lines))
|
||||
self.ssh.upload_file_sudo('\n'.join(new_lines).replace('\r\n', '\n'), "/opt/amnezia/telemt/config.toml")
|
||||
|
||||
def toggle_client(self, protocol_type, client_id, enable, restart=True):
|
||||
# API doesn't have a direct "toggle", so we either set a huge quota or remove/re-add
|
||||
# But for Telemt, commenting out in config is the persistent way.
|
||||
# We'll use HUP after toggling in config.
|
||||
|
||||
config_text = self._get_server_config()
|
||||
lines = config_text.split('\n')
|
||||
new_lines = []
|
||||
@@ -373,10 +440,21 @@ class TelemtManager:
|
||||
line = base_line if enable else f"# {base_line}"
|
||||
new_lines.append(line)
|
||||
|
||||
if restart:
|
||||
self.save_server_config(protocol_type, '\n'.join(new_lines))
|
||||
self.ssh.upload_file_sudo('\n'.join(new_lines).replace('\r\n', '\n'), "/opt/amnezia/telemt/config.toml")
|
||||
|
||||
if enable:
|
||||
# If enabling, we re-add via API since it might have been deleted from memory
|
||||
secret = ""
|
||||
users = self._parse_users_from_config('\n'.join(new_lines))
|
||||
secret = users.get(client_id, "")
|
||||
if secret:
|
||||
self._api_request("POST", "/v1/users", data={"username": client_id, "secret": secret})
|
||||
else:
|
||||
self.ssh.upload_file_sudo('\n'.join(new_lines).replace('\r\n', '\n'), "/opt/amnezia/telemt/config.toml")
|
||||
# If disabling, we just delete from memory
|
||||
self._api_request("DELETE", f"/v1/users/{client_id}")
|
||||
|
||||
if restart:
|
||||
self.ssh.run_sudo_command(f"docker kill -s HUP {self.CONTAINER_NAME} || docker restart {self.CONTAINER_NAME}")
|
||||
|
||||
def get_client_config(self, protocol_type, client_id, host='', port=''):
|
||||
resp = self._api_request("GET", f"/v1/users/{client_id}")
|
||||
|
||||
+22
-1
@@ -383,6 +383,18 @@
|
||||
|
||||
<div id="telemtConnOptions"
|
||||
style="display:none; padding: var(--space-md); background: rgba(0,0,0,0.03); border-radius: var(--radius-md); margin-bottom: var(--space-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Secret (Hex 32 chars)</label>
|
||||
<input class="form-input" type="text" id="telemtSecret" placeholder="Optional (auto-generated if empty)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AD Tag (Hex 32 chars)</label>
|
||||
<input class="form-input" type="text" id="telemtAdTag" placeholder="Optional">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Max TCP Conns</label>
|
||||
<input class="form-input" type="number" id="telemtMaxConns" placeholder="Optional (e.g. 1000)" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Quota (Bytes)</label>
|
||||
<input class="form-input" type="number" id="telemtQuota" placeholder="Optional (e.g. 1073741824)"
|
||||
@@ -1039,6 +1051,9 @@
|
||||
params.telemt_quota = document.getElementById('telemtQuota').value || null;
|
||||
params.telemt_max_ips = parseInt(document.getElementById('telemtMaxIps').value) || null;
|
||||
params.telemt_expiry = document.getElementById('telemtExpiry').value || null;
|
||||
params.telemt_secret = document.getElementById('telemtSecret').value || null;
|
||||
params.telemt_ad_tag = document.getElementById('telemtAdTag').value || null;
|
||||
params.telemt_max_conns = parseInt(document.getElementById('telemtMaxConns').value) || null;
|
||||
if (isEditingConn) params.client_id = editingClientId;
|
||||
}
|
||||
const result = await apiCall(isEditingConn ? `/api/servers/${SERVER_ID}/connections/edit` : `/api/servers/${SERVER_ID}/connections/add`, 'POST', params);
|
||||
@@ -1090,12 +1105,18 @@
|
||||
if (isEdit && clientData) {
|
||||
editingClientId = clientData.clientName;
|
||||
document.getElementById('telemtQuota').value = clientData.quota || '';
|
||||
document.getElementById('telemtMaxIps').value = clientData.active_ips || ''; // actually max_ips isn't stored in api data yet, assume it was active_ips for now or better - just empty
|
||||
document.getElementById('telemtMaxIps').value = '';
|
||||
document.getElementById('telemtExpiry').value = clientData.expiry || '';
|
||||
document.getElementById('telemtSecret').value = clientData.token || '';
|
||||
document.getElementById('telemtAdTag').value = clientData.user_ad_tag || '';
|
||||
document.getElementById('telemtMaxConns').value = clientData.max_tcp_conns || '';
|
||||
} else {
|
||||
document.getElementById('telemtQuota').value = '';
|
||||
document.getElementById('telemtMaxIps').value = '';
|
||||
document.getElementById('telemtExpiry').value = '';
|
||||
document.getElementById('telemtSecret').value = '';
|
||||
document.getElementById('telemtAdTag').value = '';
|
||||
document.getElementById('telemtMaxConns').value = '';
|
||||
}
|
||||
} else {
|
||||
telemtOpts.style.display = 'none';
|
||||
|
||||
@@ -145,6 +145,33 @@
|
||||
placeholder="{{ _('connection_name_placeholder') }}">
|
||||
</div>
|
||||
|
||||
<div id="newUserTelemtOptions" style="display:none; padding: var(--space-md); background: rgba(0,0,0,0.03); border-radius: var(--radius-md); margin-bottom: var(--space-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Secret (Hex 32 chars)</label>
|
||||
<input class="form-input" type="text" id="newUserTelemtSecret" placeholder="Optional">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AD Tag (Hex 32 chars)</label>
|
||||
<input class="form-input" type="text" id="newUserTelemtAdTag" placeholder="Optional">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Max TCP Conns</label>
|
||||
<input class="form-input" type="number" id="newUserTelemtMaxConns" placeholder="Optional" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Quota (Bytes)</label>
|
||||
<input class="form-input" type="number" id="newUserTelemtQuota" placeholder="Optional" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Max Unique IPs</label>
|
||||
<input class="form-input" type="number" id="newUserTelemtMaxIps" placeholder="Optional" min="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Expiration Date (ISO 8601)</label>
|
||||
<input class="form-input" type="text" id="newUserTelemtExpiry" placeholder="e.g. 2026-12-31T23:59:59Z">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeModal('addUserModal')">{{ _('cancel')
|
||||
}}</button>
|
||||
@@ -261,6 +288,33 @@
|
||||
<input class="form-input" type="text" id="ucName" placeholder="{{ _('connection_name_placeholder') }}">
|
||||
</div>
|
||||
|
||||
<div id="ucTelemtOptions" style="display:none; padding: var(--space-md); background: rgba(0,0,0,0.03); border-radius: var(--radius-md); margin-bottom: var(--space-md);">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Secret (Hex 32 chars)</label>
|
||||
<input class="form-input" type="text" id="ucTelemtSecret" placeholder="Optional">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AD Tag (Hex 32 chars)</label>
|
||||
<input class="form-input" type="text" id="ucTelemtAdTag" placeholder="Optional">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Max TCP Conns</label>
|
||||
<input class="form-input" type="number" id="ucTelemtMaxConns" placeholder="Optional" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Quota (Bytes)</label>
|
||||
<input class="form-input" type="number" id="ucTelemtQuota" placeholder="Optional" min="0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Max Unique IPs</label>
|
||||
<input class="form-input" type="number" id="ucTelemtMaxIps" placeholder="Optional" min="1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Expiration Date (ISO 8601)</label>
|
||||
<input class="form-input" type="text" id="ucTelemtExpiry" placeholder="e.g. 2026-12-31T23:59:59Z">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" onclick="closeModal('addUserConnModal')">{{ _('cancel') }}</button>
|
||||
<button class="btn btn-primary" onclick="doAddUserConnection()" id="ucAddBtn">
|
||||
@@ -509,8 +563,19 @@
|
||||
const show = e.target.value !== '';
|
||||
updateProtocols(e.target.value, 'newUserProtocol', 'newUserProtoGroup');
|
||||
document.getElementById('newUserConnNameGroup').style.display = show ? '' : 'none';
|
||||
updateTelemtOptions('newUserProtocol', 'newUserTelemtOptions');
|
||||
});
|
||||
|
||||
document.getElementById('newUserProtocol').addEventListener('change', (e) => {
|
||||
updateTelemtOptions('newUserProtocol', 'newUserTelemtOptions');
|
||||
});
|
||||
|
||||
function updateTelemtOptions(protoSelectId, optionsId) {
|
||||
const proto = document.getElementById(protoSelectId).value;
|
||||
const options = document.getElementById(optionsId);
|
||||
if (options) options.style.display = (proto === 'telemt') ? 'block' : 'none';
|
||||
}
|
||||
|
||||
document.getElementById('ucServer').addEventListener('change', (e) => {
|
||||
updateProtocols(e.target.value, 'ucProtocol');
|
||||
if (document.getElementById('ucMode').value === 'existing') {
|
||||
@@ -522,6 +587,7 @@
|
||||
if (document.getElementById('ucMode').value === 'existing') {
|
||||
fetchExistingClients();
|
||||
}
|
||||
updateTelemtOptions('ucProtocol', 'ucTelemtOptions');
|
||||
});
|
||||
|
||||
async function addUser(e) {
|
||||
@@ -549,6 +615,14 @@
|
||||
body.server_id = parseInt(serverId);
|
||||
body.protocol = document.getElementById('newUserProtocol').value;
|
||||
body.connection_name = document.getElementById('newUserConnName').value || null;
|
||||
if (body.protocol === 'telemt') {
|
||||
body.telemt_quota = document.getElementById('newUserTelemtQuota').value || null;
|
||||
body.telemt_max_ips = parseInt(document.getElementById('newUserTelemtMaxIps').value) || null;
|
||||
body.telemt_expiry = document.getElementById('newUserTelemtExpiry').value || null;
|
||||
body.telemt_secret = document.getElementById('newUserTelemtSecret').value || null;
|
||||
body.telemt_ad_tag = document.getElementById('newUserTelemtAdTag').value || null;
|
||||
body.telemt_max_conns = parseInt(document.getElementById('newUserTelemtMaxConns').value) || null;
|
||||
}
|
||||
}
|
||||
const result = await apiCall('/api/users/add', 'POST', body);
|
||||
showToast(_('success'), 'success');
|
||||
@@ -669,6 +743,13 @@
|
||||
const clientId = document.getElementById('ucExistingClient').value;
|
||||
if (!clientId) throw new Error(_('select_connection'));
|
||||
body.client_id = clientId;
|
||||
} else if (body.protocol === 'telemt') {
|
||||
body.telemt_quota = document.getElementById('ucTelemtQuota').value || null;
|
||||
body.telemt_max_ips = parseInt(document.getElementById('ucTelemtMaxIps').value) || null;
|
||||
body.telemt_expiry = document.getElementById('ucTelemtExpiry').value || null;
|
||||
body.telemt_secret = document.getElementById('ucTelemtSecret').value || null;
|
||||
body.telemt_ad_tag = document.getElementById('ucTelemtAdTag').value || null;
|
||||
body.telemt_max_conns = parseInt(document.getElementById('ucTelemtMaxConns').value) || null;
|
||||
}
|
||||
|
||||
const result = await apiCall(`/api/users/${currentUserId}/connections/add`, 'POST', body);
|
||||
|
||||
Reference in new issue
Block a user