Добавил загрузку конфигов WG

This commit is contained in:
Petro1990 2025-11-26 13:35:05 +03:00
parent 9354ab3958
commit 39a8308f16
1 changed files with 250 additions and 56 deletions

View File

@ -319,12 +319,95 @@ def insert_proxy_logic(content, proxy_name, target_groups):
return "\n".join(new_lines)
def replace_proxy_block(content, target_name, new_yaml_lines):
lines = content.splitlines()
new_content_lines = []
in_proxies = False
found_target = False
replaced = False
# Регулярка для поиска начала блока прокси с указанным именем
# Учитывает кавычки и пробелы: - name: "target_name"
name_pattern = re.compile(r'^\s*-\s+name:\s*(["\'])?' + re.escape(target_name) + r'(\1)?\s*$')
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
# Определяем секцию proxies
if stripped.startswith('proxies:'):
in_proxies = True
new_content_lines.append(line)
i += 1
continue
# Если вышли из proxies (новая секция начинается без отступа)
if in_proxies and line and not line.startswith(' ') and not line.startswith('\t') and not line.startswith('#'):
in_proxies = False
if in_proxies and not replaced:
if name_pattern.match(stripped):
# Нашли целевой прокси.
# 1. Определяем отступ этого блока (обычно 2 пробела)
indent_len = len(line) - len(line.lstrip())
# 2. Добавляем новый YAML.
# new_yaml_lines приходят без отступов (или с базовыми).
# Нам нужно убедиться, что первая строка имеет дефис, а остальные - отступ.
# Обычно new_yaml_lines[0] уже "- name: ...".
# Просто добавим отступ всем строкам.
# Принудительно меняем имя в новом YAML на целевое, чтобы сохранить структуру
# (хотя парсеры уже могли вернуть новое имя, но лучше перестраховаться)
if new_yaml_lines and "name:" in new_yaml_lines[0]:
# Заменяем имя в первой строке нового конфига на старое (target_name)
new_yaml_lines[0] = re.sub(r'name:\s*".*"', f'name: "{target_name}"', new_yaml_lines[0])
for n_line in new_yaml_lines:
new_content_lines.append(" " * indent_len + n_line)
replaced = True
found_target = True
# 3. Пропускаем старый блок
# Читаем дальше, пока не найдем строку с ТАКИМ ЖЕ отступом, начинающуюся с '-' (следующий элемент списка)
# или строку с МЕНЬШИМ отступом (конец секции)
i += 1
while i < len(lines):
next_line = lines[i]
next_stripped = next_line.strip()
next_indent = len(next_line) - len(next_line.lstrip())
if not next_stripped: # Пустые строки пропускаем/удаляем внутри блока
i += 1
continue
if next_indent < indent_len:
# Конец секции proxies
break
if next_indent == indent_len and next_stripped.startswith('-'):
# Следующий элемент списка
break
# Это всё еще часть старого блока, пропускаем
i += 1
continue
new_content_lines.append(line)
i += 1
return "\n".join(new_content_lines)
HTML_TEMPLATE = """<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<title>Mihomo Editor v18.7</title>
<title>Mihomo Editor v18.8</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.7/ace.js"></script>
<style>
:root {
@ -412,6 +495,8 @@ button:hover{filter:brightness(1.1)}
.prof-btns { display: flex; gap: 8px; margin-top: 5px; }
.prof-btns button { flex: 1; }
.proxy-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
#last-load {
font-size: 11px;
color: var(--txt);
@ -469,7 +554,7 @@ button:hover{filter:brightness(1.1)}
<div class="hdr">
<div style="display:flex;align-items:center;gap:10px">
<h2 style="margin:0;color:#4caf50">Mihomo Studio</h2>
<span style="color:var(--txt-sec);font-size:12px">v18.7 Auto-Panel</span>
<span style="color:var(--txt-sec);font-size:12px">v18.8 Auto-Panel</span>
</div>
<div id="last-load">Loaded: __TIME__</div>
</div>
@ -501,9 +586,12 @@ button:hover{filter:brightness(1.1)}
</div>
<div class="sec">
<h3>Управление прокси</h3>
<button onclick="openAddProxyModal()" class="btn-s" style="width:100%"> Добавить прокси</button>
<button onclick="showDel()" class="btn-d" style="width:100%">🗑 Удалить прокси</button>
<button onclick="showRename()" class="btn-u" style="width:100%"> Переименовать прокси</button>
<div class="proxy-grid">
<button onclick="openAddProxyModal()" class="btn-s"> Добавить</button>
<button onclick="openEditProxyModal()" class="btn-u"> Изменить</button>
<button onclick="showRename()" class="btn-g">Aa Переимен.</button>
<button onclick="showDel()" class="btn-d">🗑 Удалить</button>
</div>
</div>
<div class="sec">
<h3>Бэкапы</h3>
@ -555,9 +643,16 @@ button:hover{filter:brightness(1.1)}
<div id="addProxyModal" class="ovl"><div class="mod">
<div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid var(--bd); padding-bottom:10px; margin-bottom:0;">
<h3 style="margin:0; padding:0; border:0;">Добавить прокси</h3>
<h3 id="proxyModalTitle" style="margin:0; padding:0; border:0;">Добавить прокси</h3>
<button onclick="closeM('addProxyModal')" style="width:32px; height:32px; padding:0; background:var(--bg-ter); color:var(--txt); font-size:18px;"></button>
</div>
<div id="edit-proxy-container" style="display:none; margin-bottom:10px;">
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Выберите прокси для изменения:</label>
<select id="edit-proxy-sel"></select>
<div style="font-size:11px; color:var(--btn-u); margin-top:5px;"> Данные этого прокси будут полностью заменены новыми!</div>
</div>
<div class="modal-tabs">
<button class="active" onclick="switchTab(event, 'vlessTab')">VLESS</button>
<button onclick="switchTab(event, 'wgTab')">WireGuard</button>
@ -566,19 +661,27 @@ button:hover{filter:brightness(1.1)}
<div id="vlessTab" class="tab-content active">
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Ссылка VLESS:</label>
<input id="vlessLink" placeholder="vless://..." style="margin-bottom:10px;">
<div id="vless-name-block">
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Имя прокси (необязательно):</label>
<input id="vlessProxyName" placeholder="Автоматически из ссылки" style="margin-bottom:10px;">
<button onclick="parseVless()" class="btn-s" style="width:100%; justify-content:center;">Добавить</button>
</div>
<button onclick="parseVless()" class="btn-s" style="width:100%; justify-content:center;">Сохранить</button>
</div>
<div id="wgTab" class="tab-content">
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Конфигурация WireGuard:</label>
<textarea id="wgConfig" rows="8" placeholder="Вставьте содержимое .conf файла сюда..." style="width:100%; margin-bottom:10px;"></textarea>
<div id="wg-name-block">
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Имя прокси (необязательно):</label>
<input id="wgProxyName" placeholder="Автоматически из Endpoint" style="margin-bottom:10px;">
</div>
<input type="file" id="wgFile" accept=".conf" style="display:none" onchange="loadWgFile(this)">
<button onclick="document.getElementById('wgFile').click()" class="btn-u" style="width:100%; justify-content:center; margin-bottom:10px;">📂 Или загрузить .conf файл</button>
<button onclick="addWireguard()" class="btn-s" style="width:100%; justify-content:center;">Добавить</button>
<button onclick="addWireguard()" class="btn-s" style="width:100%; justify-content:center;">Сохранить</button>
</div>
</div></div>
@ -595,6 +698,7 @@ button:hover{filter:brightness(1.1)}
var ed=ace.edit("ed");ed.setTheme("ace/theme/monokai");ed.session.setMode("ace/mode/yaml");ed.setOptions({fontSize:14,tabSize:2,useSoftTabs:true});
var pData=null, GRP_KEY="mihomo_grp_sel", LIM_KEY="mihomo_bk_lim", THM_KEY="mihomo_theme";
var initialConfig = __JSON_CONTENT__;
var isEditMode = false;
// Открываем панель через наш локальный прокси (безопасно для PNA/CORS)
function openPanel() {
@ -604,6 +708,7 @@ function openPanel() {
ed.setValue(initialConfig); ed.clearSelection();
document.getElementById('vlessLink').addEventListener('input', function() {
if(isEditMode) return; // В режиме редактирования имя берется из селекта
var link = this.value;
if (link.startsWith("vless://") && link.includes("#")) {
var name = link.split('#')[1];
@ -612,6 +717,7 @@ document.getElementById('vlessLink').addEventListener('input', function() {
});
document.getElementById('wgConfig').addEventListener('input', function() {
if(isEditMode) return;
var conf = this.value;
var nameField = document.getElementById('wgProxyName');
var endpointMatch = conf.match(/Endpoint\s*=\s*(.+)/);
@ -789,26 +895,78 @@ function restoreBackup(fname){
});
}
function getProxiesList() {
var ls = ed.getValue().split(/\\r?\\n/);
var prs = [], inP = 0;
for (var l of ls) {
if (l.match(/^proxies:/)) inP = 1;
if (inP && l.match(/^[a-zA-Z]/) && !l.match(/^proxies:/)) inP = 0;
if (inP) {
var m = l.match(/^\s+-\s+name:\s+(.*)/);
if (m) prs.push(m[1].trim().replace(/^['"]|['"]$/g, ''))
}
}
return prs;
}
function openAddProxyModal() {
isEditMode = false;
document.getElementById('proxyModalTitle').innerText = "Добавить прокси";
document.getElementById('edit-proxy-container').style.display = 'none';
document.getElementById('vless-name-block').style.display = 'block';
document.getElementById('wg-name-block').style.display = 'block';
// Clear inputs
document.getElementById('vlessLink').value = '';
document.getElementById('vlessProxyName').value = '';
document.getElementById('wgConfig').value = '';
document.getElementById('wgProxyName').value = '';
document.getElementById('addProxyModal').style.display = 'flex';
}
function openEditProxyModal() {
isEditMode = true;
document.getElementById('proxyModalTitle').innerText = "Изменить прокси";
document.getElementById('edit-proxy-container').style.display = 'block';
document.getElementById('vless-name-block').style.display = 'none';
document.getElementById('wg-name-block').style.display = 'none';
// Populate select
var prs = getProxiesList();
var sel = document.getElementById('edit-proxy-sel');
sel.innerHTML = '';
if(prs.length === 0) {
var o = document.createElement('option');
o.text = "Нет прокси для редактирования";
sel.add(o);
sel.disabled = true;
} else {
sel.disabled = false;
prs.forEach(p => {
var o = document.createElement('option');
o.text = p;
sel.add(o);
});
}
// Clear inputs
document.getElementById('vlessLink').value = '';
document.getElementById('wgConfig').value = '';
document.getElementById('addProxyModal').style.display = 'flex';
}
function switchTab(evt, tabName) {
var i, tabcontent, tablinks;
// Сначала скрываем все панели с контентом
tabcontent = document.getElementsByClassName("tab-content");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].classList.remove("active");
}
// Затем убираем активный класс со всех кнопок-вкладок
tablinks = document.getElementsByClassName("modal-tabs")[0].getElementsByTagName("button");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].classList.remove("active");
}
// И только потом показываем нужную вкладку и делаем ее активной
document.getElementById(tabName).classList.add("active");
evt.currentTarget.classList.add("active");
}
@ -820,7 +978,6 @@ function loadWgFile(input) {
r.onload = function(e) {
var content = e.target.result;
document.getElementById('wgConfig').value = content;
// Trigger the input event to auto-fill the name
document.getElementById('wgConfig').dispatchEvent(new Event('input'));
};
r.readAsText(f);
@ -829,53 +986,97 @@ function loadWgFile(input) {
function addWireguard() {
var conf = document.getElementById('wgConfig').value;
var name = document.getElementById('wgProxyName').value.trim();
var name = '';
if(isEditMode) {
name = document.getElementById('edit-proxy-sel').value;
if(!name || document.getElementById('edit-proxy-sel').disabled) return alert("Выберите прокси для редактирования");
} else {
name = document.getElementById('wgProxyName').value.trim();
}
if (!conf) return alert("Конфигурация WireGuard не может быть пустой.");
var p = new URLSearchParams();
p.append('act', 'add_wireguard');
p.append('config_text', conf);
if (name) {
p.append('proxy_name', name);
}
if (name) p.append('proxy_name', name);
fetch('/', { method: 'POST', body: p })
.then(r => r.json())
.then(d => {
if (d.error) {
alert(d.error);
} else {
if(isEditMode) {
replaceProxyData(name, d.yaml);
} else {
pData = d;
closeM('addProxyModal');
document.getElementById('wgConfig').value = '';
document.getElementById('wgProxyName').value = '';
showG();
}
}
});
}
function parseVless(){
var link = document.getElementById('vlessLink').value;
var name = document.getElementById('vlessProxyName').value.trim();
var name = '';
if(isEditMode) {
name = document.getElementById('edit-proxy-sel').value;
if(!name || document.getElementById('edit-proxy-sel').disabled) return alert("Выберите прокси для редактирования");
} else {
name = document.getElementById('vlessProxyName').value.trim();
}
if (!link) return;
var p = new URLSearchParams();
p.append('act', 'parse');
p.append('link', link);
if (name) {
p.append('proxy_name', name);
}
if (name) p.append('proxy_name', name);
fetch('/', { method: 'POST', body: p })
.then(r => r.json())
.then(d => {
if (d.error) {
alert(d.error);
} else {
if(isEditMode) {
replaceProxyData(name, d.yaml);
} else {
pData = d;
closeM('addProxyModal');
document.getElementById('vlessLink').value = '';
document.getElementById('vlessProxyName').value = '';
showG();
}
}
});
}
function replaceProxyData(targetName, newYaml) {
if(!confirm("Заменить данные прокси '" + targetName + "'?")) return;
var content = ed.getValue();
var p = new URLSearchParams();
p.append('act', 'replace_proxy');
p.append('target_name', targetName);
p.append('new_yaml', newYaml);
p.append('content', content);
fetch('/', { method: 'POST', body: p })
.then(r => r.json())
.then(d => {
if (d.error) {
alert(d.error);
} else {
ed.setValue(d.new_content);
ed.clearSelection();
closeM('addProxyModal');
showToast("✏️ Данные прокси обновлены");
}
});
}
function showG(){
var txt=ed.getValue(); var ls=txt.split(/\\r?\\n/); var grps=[], inG=false;
for(var l of ls){ if(l.match(/^proxy-groups:/))inG=true; if(inG && l.match(/^[a-zA-Z]/) && !l.match(/^proxy-groups:/))inG=false; if(inG){var m=l.match(/^\s*-\s+name:\s+(.*)/);if(m)grps.push(m[1].trim().replace(/^['"]|['"]$/g,''))}}
@ -896,8 +1097,7 @@ function applyVless(){
fetch('/',{method:'POST',body:p}).then(r=>r.json()).then(d=>{if(d.error)alert(d.error);else{ed.setValue(d.new_content);ed.clearSelection();showToast("✅ Добавлено")}});
}
function showDel(){
var ls=ed.getValue().split(/\\r?\\n/); var prs=[], inP=0;
for(var l of ls){ if(l.match(/^proxies:/))inP=1; if(inP && l.match(/^[a-zA-Z]/) && !l.match(/^proxies:/))inP=0; if(inP){var m=l.match(/^\s+-\s+name:\s+(.*)/);if(m)prs.push(m[1].trim().replace(/^['"]|['"]$/g,''))}}
var prs = getProxiesList();
var s=document.getElementById('sel-del');s.innerHTML='';
prs.forEach(p=>{var o=document.createElement('option');o.text=p;s.add(o)});
document.getElementById('m-del').style.display='flex';
@ -939,16 +1139,7 @@ function doDel(){
}
function showRename() {
var ls = ed.getValue().split(/\\r?\\n/);
var prs = [], inP = 0;
for (var l of ls) {
if (l.match(/^proxies:/)) inP = 1;
if (inP && l.match(/^[a-zA-Z]/) && !l.match(/^proxies:/)) inP = 0;
if (inP) {
var m = l.match(/^\s+-\s+name:\s+(.*)/);
if (m) prs.push(m[1].trim().replace(/^['"]|['"]$/g, ''))
}
}
var prs = getProxiesList();
var s = document.getElementById('sel-ren-proxy');
s.innerHTML = '';
prs.forEach(p => {
@ -1185,22 +1376,15 @@ class H(http.server.SimpleHTTPRequestHandler):
return
# 1. Замена в определении прокси: - name: "old_name"
# Regex для поиска `name: 'old_name'`, `name: "old_name"` или `name: old_name`
# Используем `re.escape` для безопасности
escaped_old = re.escape(old_name)
# (?P<quote>['"]?) - захватывает кавычку (если она есть) в группу 'quote'
# \\1 - ссылается на захваченную кавычку, чтобы заменить на такую же
pattern_def = r"(name\s*:\s*)(?P<quote>['\"]?)" + escaped_old + r"(?P=quote)"
# Заменяем, сохраняя оригинальные кавычки
content = re.sub(pattern_def, r'\g<1>"' + new_name + '"', content, count=1)
# 2. Замена в списках proxy-groups: - "old_name" (Block Style)
# Regex для поиска `- 'old_name'`, `- "old_name"` или `- old_name`
pattern_list = r"(-\s+)(?P<quote>['\"]?)" + escaped_old + r"(?P=quote)"
content = re.sub(pattern_list, r'\g<1>"' + new_name + '"', content)
# 3. Замена в Inline Lists: [ ..., "old_name", ... ]
# Ищем old_name внутри delimiters [ или , с последующим , или ]
pattern_inline = r"([\[,]\s*)(?P<q>['\"]?)" + escaped_old + r"(?P=q)(\s*[,\]])"
content = re.sub(pattern_inline, r'\1\g<q>' + new_name + r'\g<q>\3', content)
@ -1249,6 +1433,16 @@ class H(http.server.SimpleHTTPRequestHandler):
s.wfile.write(json.dumps({'new_content': uc}).encode('utf-8'));
return
if a == 'replace_proxy':
target_name = p.get('target_name', '')
new_yaml = p.get('new_yaml', '')
content = p.get('content', '')
new_yaml_lines = new_yaml.splitlines()
uc = replace_proxy_block(content, target_name, new_yaml_lines)
s.wfile.write(json.dumps({'new_content': uc}).encode('utf-8'))
return
if a == 'clean_backups':
limit = int(p.get('limit', 5))
files = sorted(glob.glob(BACKUP_DIR + "/*.yaml"), key=os.path.getmtime, reverse=True)