Implement /restore API
This commit is contained in:
@ -180,7 +180,8 @@ def backup_hysteria2():
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def restore_hysteria2(backup_file_path):
|
|
||||||
|
def restore_hysteria2(backup_file_path: str):
|
||||||
'''Restores Hysteria configuration from the given backup file.'''
|
'''Restores Hysteria configuration from the given backup file.'''
|
||||||
try:
|
try:
|
||||||
run_cmd(['bash', Command.RESTORE_HYSTERIA2.value, backup_file_path])
|
run_cmd(['bash', Command.RESTORE_HYSTERIA2.value, backup_file_path])
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException, UploadFile, File
|
||||||
from ..schema.config.hysteria import ConfigFile, GetPortResponse, GetSniResponse
|
from ..schema.config.hysteria import ConfigFile, GetPortResponse, GetSniResponse
|
||||||
from ..schema.response import DetailResponse
|
from ..schema.response import DetailResponse
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
import shutil
|
||||||
# from ..schema.config.hysteria import InstallInputBody
|
# from ..schema.config.hysteria import InstallInputBody
|
||||||
import os
|
import os
|
||||||
import cli_api
|
import cli_api
|
||||||
@ -150,20 +151,20 @@ async def set_sni_api(sni: str):
|
|||||||
|
|
||||||
|
|
||||||
@router.get('/backup', response_class=FileResponse, summary='Backup Hysteria2 configuration')
|
@router.get('/backup', response_class=FileResponse, summary='Backup Hysteria2 configuration')
|
||||||
async def backup():
|
async def backup_api():
|
||||||
"""
|
"""
|
||||||
Backups the Hysteria2 configuration and sends the backup ZIP file.
|
Backups the Hysteria2 configuration and sends the backup ZIP file.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cli_api.backup_hysteria2()
|
cli_api.backup_hysteria2()
|
||||||
backup_dir = "/opt/hysbackup/"
|
backup_dir = "/opt/hysbackup/" # TODO: get this path from .env
|
||||||
|
|
||||||
if not os.path.isdir(backup_dir):
|
if not os.path.isdir(backup_dir):
|
||||||
raise HTTPException(status_code=500, detail="Backup directory does not exist.")
|
raise HTTPException(status_code=500, detail="Backup directory does not exist.")
|
||||||
|
|
||||||
files = [f for f in os.listdir(backup_dir) if f.endswith('.zip')]
|
files = [f for f in os.listdir(backup_dir) if f.endswith('.zip')]
|
||||||
files.sort(key=lambda x: os.path.getctime(os.path.join(backup_dir, x)), reverse=True)
|
files.sort(key=lambda x: os.path.getctime(os.path.join(backup_dir, x)), reverse=True)
|
||||||
latest_backup_file = files[0] if files else None
|
latest_backup_file = files[0] if files else None
|
||||||
|
|
||||||
if latest_backup_file:
|
if latest_backup_file:
|
||||||
backup_file_path = os.path.join(backup_dir, latest_backup_file)
|
backup_file_path = os.path.join(backup_dir, latest_backup_file)
|
||||||
@ -182,6 +183,24 @@ async def backup():
|
|||||||
raise HTTPException(status_code=500, detail=f'Error: {str(e)}')
|
raise HTTPException(status_code=500, detail=f'Error: {str(e)}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/restore', response_model=DetailResponse, summary='Restore Hysteria2 Configuration')
|
||||||
|
async def restore_api(f: UploadFile = File(...)):
|
||||||
|
try:
|
||||||
|
dst_dir_path = '/opt/hysbackup/' # TODO: get this path from .env
|
||||||
|
if not os.path.isdir(dst_dir_path): # TODO: the dir path should be exist, so no need to check
|
||||||
|
os.makedirs(dst_dir_path)
|
||||||
|
|
||||||
|
dst_path = os.path.join(dst_dir_path, f.filename) # type: ignore
|
||||||
|
|
||||||
|
with open(dst_path, 'wb') as buffer:
|
||||||
|
shutil.copyfileobj(f.file, buffer)
|
||||||
|
|
||||||
|
cli_api.restore_hysteria2(dst_path)
|
||||||
|
return DetailResponse(detail='Hysteria2 restored successfully.')
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=400, detail=f'Error: {str(e)}')
|
||||||
|
|
||||||
|
|
||||||
@router.get('/enable-obfs', response_model=DetailResponse, summary='Enable Hysteria2 obfs')
|
@router.get('/enable-obfs', response_model=DetailResponse, summary='Enable Hysteria2 obfs')
|
||||||
async def enable_obfs():
|
async def enable_obfs():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -19,65 +19,84 @@
|
|||||||
<div class='col-lg-12'>
|
<div class='col-lg-12'>
|
||||||
<div class='card card-primary card-outline card-tabs'>
|
<div class='card card-primary card-outline card-tabs'>
|
||||||
<div class='card-header p-0 pt-1 border-bottom-0'>
|
<div class='card-header p-0 pt-1 border-bottom-0'>
|
||||||
<ul class='nav nav-pills' id='custom-tabs-three-tab' role='tablist' style="margin-left: 20px; margin-top: 10px;">
|
<ul class='nav nav-pills' id='custom-tabs-three-tab' role='tablist'
|
||||||
|
style="margin-left: 20px; margin-top: 10px;">
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link active' id='subs-tab' data-toggle='pill' href='#subs' role='tab' aria-controls='subs' aria-selected='false'><i class="fas fa-link"></i> Subscriptions</a>
|
<a class='nav-link active' id='subs-tab' data-toggle='pill' href='#subs' role='tab'
|
||||||
|
aria-controls='subs' aria-selected='false'><i class="fas fa-link"></i>
|
||||||
|
Subscriptions</a>
|
||||||
</li>
|
</li>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link' id='telegram-tab' data-toggle='pill' href='#telegram' role='tab' aria-controls='telegram' aria-selected='true'><i class="fab fa-telegram"></i> Telegram
|
<a class='nav-link' id='telegram-tab' data-toggle='pill' href='#telegram' role='tab'
|
||||||
|
aria-controls='telegram' aria-selected='true'><i class="fab fa-telegram"></i>
|
||||||
|
Telegram
|
||||||
Bot</a>
|
Bot</a>
|
||||||
</li>
|
</li>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link' id='port-tab' data-toggle='pill' href='#port' role='tab' aria-controls='port' aria-selected='false'><i class="fas fa-server"></i> Change Port</a>
|
<a class='nav-link' id='port-tab' data-toggle='pill' href='#port' role='tab'
|
||||||
|
aria-controls='port' aria-selected='false'><i class="fas fa-server"></i> Change
|
||||||
|
Port</a>
|
||||||
</li>
|
</li>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link' id='sni-tab' data-toggle='pill' href='#sni' role='tab' aria-controls='sni' aria-selected='false'><i class="fas fa-shield-alt"></i> Change SNI</a>
|
<a class='nav-link' id='sni-tab' data-toggle='pill' href='#sni' role='tab'
|
||||||
|
aria-controls='sni' aria-selected='false'><i class="fas fa-shield-alt"></i> Change
|
||||||
|
SNI</a>
|
||||||
</li>
|
</li>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link' id='ip-tab' data-toggle='pill' href='#change_ip' role='tab' aria-controls='change_ip' aria-selected='false'><i class="fas fa-network-wired"></i> Change IP</a>
|
<a class='nav-link' id='ip-tab' data-toggle='pill' href='#change_ip' role='tab'
|
||||||
|
aria-controls='change_ip' aria-selected='false'><i class="fas fa-network-wired"></i>
|
||||||
|
Change IP</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- New Backup Tab -->
|
<!-- New Backup Tab -->
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link' id='backup-tab' data-toggle='pill' href='#backup' role='tab' aria-controls='backup' aria-selected='false'><i class="fas fa-download"></i> Backup</a>
|
<a class='nav-link' id='backup-tab' data-toggle='pill' href='#backup' role='tab'
|
||||||
|
aria-controls='backup' aria-selected='false'><i class="fas fa-download"></i>
|
||||||
|
Backup</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class='card-body' style="margin-left: 25px;">
|
<div class='card-body' style="margin-left: 25px;">
|
||||||
<div class='tab-content' id='custom-tabs-three-tabContent'>
|
<div class='tab-content' id='custom-tabs-three-tabContent'>
|
||||||
|
|
||||||
<!-- Subscriptions Tab -->
|
<!-- Subscriptions Tab -->
|
||||||
<div class='tab-pane fade show active' id='subs' role='tabpanel' aria-labelledby='subs-tab'>
|
<div class='tab-pane fade show active' id='subs' role='tabpanel' aria-labelledby='subs-tab'>
|
||||||
<ul class='nav nav-tabs' id='subs-tabs' role='tablist'>
|
<ul class='nav nav-tabs' id='subs-tabs' role='tablist'>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link active' id='singbox-tab' data-toggle='tab' href='#singbox' role='tab' aria-controls='singbox'
|
<a class='nav-link active' id='singbox-tab' data-toggle='tab' href='#singbox'
|
||||||
|
role='tab' aria-controls='singbox'
|
||||||
aria-selected='true'><strong>SingBox</strong></a>
|
aria-selected='true'><strong>SingBox</strong></a>
|
||||||
</li>
|
</li>
|
||||||
<li class='nav-item'>
|
<li class='nav-item'>
|
||||||
<a class='nav-link' id='normal-tab' data-toggle='tab' href='#normal' role='tab' aria-controls='normal'
|
<a class='nav-link' id='normal-tab' data-toggle='tab' href='#normal' role='tab'
|
||||||
aria-selected='false'><strong>Normal</strong></a>
|
aria-controls='normal' aria-selected='false'><strong>Normal</strong></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class='tab-content' id='subs-tabs-content'>
|
<div class='tab-content' id='subs-tabs-content'>
|
||||||
<br>
|
<br>
|
||||||
<!-- SingBox Sub Tab -->
|
<!-- SingBox Sub Tab -->
|
||||||
<div class='tab-pane fade show active' id='singbox' role='tabpanel' aria-labelledby='singbox-tab'>
|
<div class='tab-pane fade show active' id='singbox' role='tabpanel'
|
||||||
|
aria-labelledby='singbox-tab'>
|
||||||
<form>
|
<form>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='singbox_domain'>Domain:</label>
|
<label for='singbox_domain'>Domain:</label>
|
||||||
<input type='text' class='form-control' id='singbox_domain' placeholder='Enter Domain'>
|
<input type='text' class='form-control' id='singbox_domain'
|
||||||
|
placeholder='Enter Domain'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid domain (without http:// or https://).
|
Please enter a valid domain (without http:// or https://).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='singbox_port'>Port:</label>
|
<label for='singbox_port'>Port:</label>
|
||||||
<input type='text' class='form-control' id='singbox_port' placeholder='Enter Port'>
|
<input type='text' class='form-control' id='singbox_port'
|
||||||
|
placeholder='Enter Port'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid port number.
|
Please enter a valid port number.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="singbox_start" type='button' class='btn btn-success'>Start</button>
|
<button id="singbox_start" type='button'
|
||||||
<button id="singbox_stop" type='button' class='btn btn-danger' style="display: none;">Stop</button>
|
class='btn btn-success'>Start</button>
|
||||||
|
<button id="singbox_stop" type='button' class='btn btn-danger'
|
||||||
|
style="display: none;">Stop</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -87,20 +106,24 @@
|
|||||||
<form>
|
<form>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='normal_domain'>Domain:</label>
|
<label for='normal_domain'>Domain:</label>
|
||||||
<input type='text' class='form-control' id='normal_domain' placeholder='Enter Domain'>
|
<input type='text' class='form-control' id='normal_domain'
|
||||||
|
placeholder='Enter Domain'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid domain (without http:// or https://).
|
Please enter a valid domain (without http:// or https://).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='normal_port'>Port:</label>
|
<label for='normal_port'>Port:</label>
|
||||||
<input type='text' class='form-control' id='normal_port' placeholder='Enter Port'>
|
<input type='text' class='form-control' id='normal_port'
|
||||||
|
placeholder='Enter Port'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid port number.
|
Please enter a valid port number.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="normal_start" type='button' class='btn btn-success'>Start</button>
|
<button id="normal_start" type='button'
|
||||||
<button id="normal_stop" type='button' class='btn btn-danger' style="display: none;">Stop</button>
|
class='btn btn-success'>Start</button>
|
||||||
|
<button id="normal_stop" type='button' class='btn btn-danger'
|
||||||
|
style="display: none;">Stop</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -113,20 +136,23 @@
|
|||||||
<form>
|
<form>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='telegram_api_token'>API Token:</label>
|
<label for='telegram_api_token'>API Token:</label>
|
||||||
<input type='text' class='form-control' id='telegram_api_token' placeholder='Enter API Token'>
|
<input type='text' class='form-control' id='telegram_api_token'
|
||||||
<div class="invalid-feedback">
|
placeholder='Enter API Token'>
|
||||||
Please enter a valid API Token.
|
<div class="invalid-feedback">
|
||||||
</div>
|
Please enter a valid API Token.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='telegram_admin_id'>Admin ID:</label>
|
<label for='telegram_admin_id'>Admin ID:</label>
|
||||||
<input type='text' class='form-control' id='telegram_admin_id' placeholder='Enter Admin ID'>
|
<input type='text' class='form-control' id='telegram_admin_id'
|
||||||
|
placeholder='Enter Admin ID'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid Admin ID.
|
Please enter a valid Admin ID.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="telegram_start" type='button' class='btn btn-success'>Start</button>
|
<button id="telegram_start" type='button' class='btn btn-success'>Start</button>
|
||||||
<button id="telegram_stop" type='button' class='btn btn-danger' style="display: none;">Stop</button>
|
<button id="telegram_stop" type='button' class='btn btn-danger'
|
||||||
|
style="display: none;">Stop</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -137,7 +163,8 @@
|
|||||||
<form>
|
<form>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='hysteria_port'>Port:</label>
|
<label for='hysteria_port'>Port:</label>
|
||||||
<input type='text' class='form-control' id='hysteria_port' placeholder='Enter Port'>
|
<input type='text' class='form-control' id='hysteria_port'
|
||||||
|
placeholder='Enter Port'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid port number.
|
Please enter a valid port number.
|
||||||
</div>
|
</div>
|
||||||
@ -152,7 +179,8 @@
|
|||||||
<form>
|
<form>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='sni_domain'>Domain:</label>
|
<label for='sni_domain'>Domain:</label>
|
||||||
<input type='text' class='form-control' id='sni_domain' placeholder='Enter Domain'>
|
<input type='text' class='form-control' id='sni_domain'
|
||||||
|
placeholder='Enter Domain'>
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid domain (without http:// or https://).
|
Please enter a valid domain (without http:// or https://).
|
||||||
</div>
|
</div>
|
||||||
@ -166,14 +194,16 @@
|
|||||||
<form>
|
<form>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='ipv4'>IPv4:</label>
|
<label for='ipv4'>IPv4:</label>
|
||||||
<input type='text' class='form-control' id='ipv4' placeholder='Enter IPv4' value="{{ ipv4 or '' }}">
|
<input type='text' class='form-control' id='ipv4' placeholder='Enter IPv4'
|
||||||
<div class="invalid-feedback">
|
value="{{ ipv4 or '' }}">
|
||||||
|
<div class="invalid-feedback">
|
||||||
Please enter a valid IPv4 address.
|
Please enter a valid IPv4 address.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='ipv6'>IPv6:</label>
|
<label for='ipv6'>IPv6:</label>
|
||||||
<input type='text' class='form-control' id='ipv6' placeholder='Enter IPv6' value="{{ ipv6 or '' }}">
|
<input type='text' class='form-control' id='ipv6' placeholder='Enter IPv6'
|
||||||
|
value="{{ ipv6 or '' }}">
|
||||||
<div class="invalid-feedback">
|
<div class="invalid-feedback">
|
||||||
Please enter a valid IPv6 address.
|
Please enter a valid IPv6 address.
|
||||||
</div>
|
</div>
|
||||||
@ -183,7 +213,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- Backup Tab (New) -->
|
<!-- Backup Tab (New) -->
|
||||||
<div class='tab-pane fade' id='backup' role='tabpanel' aria-labelledby='backup-tab'>
|
<div class='tab-pane fade' id='backup' role='tabpanel' aria-labelledby='backup-tab'>
|
||||||
<button id="download_backup" type='button' class='btn btn-primary'>Download Backup</button>
|
<button id="download_backup" type='button' class='btn btn-primary'>Download
|
||||||
|
Backup</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -200,7 +231,9 @@
|
|||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/js/all.min.js" integrity="sha512-yFjZbTYRCJodnuyGlsKamNE/LlEaEA/3apsIOPr7/l+jCMq9Dn9x5qyuAGqgpr4/NBZ95p8yrl/sLhJvoazg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/js/all.min.js"
|
||||||
|
integrity="sha512-yFjZbTYRCJodnuyGlsKamNE/LlEaEA/3apsIOPr7/l+jCMq9Dn9x5qyuAGqgpr4/NBZ95p8yrl/sLhJvoazg=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
@ -300,14 +333,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isValidPort(port) {
|
function isValidPort(port) {
|
||||||
if (!port) return false;
|
if (!port) return false;
|
||||||
return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535;
|
return /^[0-9]+$/.test(port) && parseInt(port) > 0 && parseInt(port) <= 65535;
|
||||||
}
|
}
|
||||||
function isValidIP(ip, version) {
|
function isValidIP(ip, version) {
|
||||||
if (!ip) return true; // Allow empty input (optional)
|
if (!ip) return true; // Allow empty input (optional)
|
||||||
|
|
||||||
if (version === 4) {
|
if (version === 4) {
|
||||||
return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip);
|
return /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(ip);
|
||||||
} else if (version === 6) {
|
} else if (version === 6) {
|
||||||
return /^(([0-9a-fA-F]{1,4}:){7,7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/.test(ip);
|
return /^(([0-9a-fA-F]{1,4}:){7,7}([0-9a-fA-F]{1,4}|:)|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/.test(ip);
|
||||||
}
|
}
|
||||||
@ -355,8 +388,8 @@
|
|||||||
// Function to validate form fields
|
// Function to validate form fields
|
||||||
|
|
||||||
function validateForm(formId) {
|
function validateForm(formId) {
|
||||||
let isValid = true;
|
let isValid = true;
|
||||||
$(`#${formId} .form-control`).each(function() {
|
$(`#${formId} .form-control`).each(function () {
|
||||||
const input = $(this);
|
const input = $(this);
|
||||||
const id = input.attr('id');
|
const id = input.attr('id');
|
||||||
|
|
||||||
@ -390,7 +423,7 @@
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (!input.val().trim()) {
|
if (!input.val().trim()) {
|
||||||
input.addClass('is-invalid');
|
input.addClass('is-invalid');
|
||||||
isValid = false;
|
isValid = false;
|
||||||
} else {
|
} else {
|
||||||
@ -403,7 +436,7 @@
|
|||||||
}
|
}
|
||||||
// Telegram Bot Start
|
// Telegram Bot Start
|
||||||
function startTelegram() {
|
function startTelegram() {
|
||||||
if (!validateForm('telegram')) return;
|
if (!validateForm('telegram')) return;
|
||||||
const apiToken = $("#telegram_api_token").val();
|
const apiToken = $("#telegram_api_token").val();
|
||||||
const adminId = $("#telegram_admin_id").val();
|
const adminId = $("#telegram_admin_id").val();
|
||||||
confirmAction("start the Telegram bot", function () {
|
confirmAction("start the Telegram bot", function () {
|
||||||
@ -457,7 +490,7 @@
|
|||||||
|
|
||||||
// Normal Subscription Start
|
// Normal Subscription Start
|
||||||
function startNormal() {
|
function startNormal() {
|
||||||
if (!validateForm('normal')) return;
|
if (!validateForm('normal')) return;
|
||||||
const domain = $("#normal_domain").val();
|
const domain = $("#normal_domain").val();
|
||||||
const port = $("#normal_port").val();
|
const port = $("#normal_port").val();
|
||||||
confirmAction("start the normal subscription", function () {
|
confirmAction("start the normal subscription", function () {
|
||||||
@ -505,7 +538,7 @@
|
|||||||
|
|
||||||
// Save IP
|
// Save IP
|
||||||
function saveIP() {
|
function saveIP() {
|
||||||
if (!validateForm('change_ip')) return;
|
if (!validateForm('change_ip')) return;
|
||||||
confirmAction("save the new IP", function () {
|
confirmAction("save the new IP", function () {
|
||||||
sendRequest(
|
sendRequest(
|
||||||
"{{ url_for('edit_ip_api') }}",
|
"{{ url_for('edit_ip_api') }}",
|
||||||
@ -521,7 +554,7 @@
|
|||||||
// Download Backup (New)
|
// Download Backup (New)
|
||||||
function downloadBackup() {
|
function downloadBackup() {
|
||||||
// No confirmation needed for a download
|
// No confirmation needed for a download
|
||||||
window.location.href = "{{ url_for('backup') }}"; // Initiate the download
|
window.location.href = "{{ url_for('backup_api') }}"; // Initiate the download
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -537,8 +570,8 @@
|
|||||||
$("#ip_change").on("click", saveIP);
|
$("#ip_change").on("click", saveIP);
|
||||||
$("#download_backup").on("click", downloadBackup); // New backup button
|
$("#download_backup").on("click", downloadBackup); // New backup button
|
||||||
|
|
||||||
// Input event listeners for real-time validation
|
// Input event listeners for real-time validation
|
||||||
$('#singbox_domain, #normal_domain, #sni_domain').on('input', function() {
|
$('#singbox_domain, #normal_domain, #sni_domain').on('input', function () {
|
||||||
if (isValidDomain($(this).val())) {
|
if (isValidDomain($(this).val())) {
|
||||||
$(this).removeClass('is-invalid');
|
$(this).removeClass('is-invalid');
|
||||||
} else {
|
} else {
|
||||||
@ -546,29 +579,29 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#singbox_port, #normal_port, #hysteria_port').on('input', function() {
|
$('#singbox_port, #normal_port, #hysteria_port').on('input', function () {
|
||||||
if (isValidPort($(this).val())) {
|
if (isValidPort($(this).val())) {
|
||||||
$(this).removeClass('is-invalid');
|
$(this).removeClass('is-invalid');
|
||||||
} else {
|
} else {
|
||||||
$(this).addClass('is-invalid');
|
$(this).addClass('is-invalid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#ipv4').on('input', function() {
|
$('#ipv4').on('input', function () {
|
||||||
if (isValidIP($(this).val(),4)) {
|
if (isValidIP($(this).val(), 4)) {
|
||||||
$(this).removeClass('is-invalid');
|
$(this).removeClass('is-invalid');
|
||||||
} else {
|
} else {
|
||||||
$(this).addClass('is-invalid');
|
$(this).addClass('is-invalid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$('#ipv6').on('input', function() {
|
$('#ipv6').on('input', function () {
|
||||||
if (isValidIP($(this).val(),6)) {
|
if (isValidIP($(this).val(), 6)) {
|
||||||
$(this).removeClass('is-invalid');
|
$(this).removeClass('is-invalid');
|
||||||
} else {
|
} else {
|
||||||
$(this).addClass('is-invalid');
|
$(this).addClass('is-invalid');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
$('#telegram_api_token, #telegram_admin_id').on('input', function() {
|
$('#telegram_api_token, #telegram_admin_id').on('input', function () {
|
||||||
if ($(this).val().trim() !== "") { // Basic check for non-empty
|
if ($(this).val().trim() !== "") { // Basic check for non-empty
|
||||||
$(this).removeClass('is-invalid');
|
$(this).removeClass('is-invalid');
|
||||||
} else {
|
} else {
|
||||||
@ -577,4 +610,4 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user