Добавил загрузку конфигов WG
This commit is contained in:
parent
9354ab3958
commit
39a8308f16
306
mihomo_editor.py
306
mihomo_editor.py
|
|
@ -319,12 +319,95 @@ def insert_proxy_logic(content, proxy_name, target_groups):
|
||||||
return "\n".join(new_lines)
|
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_TEMPLATE = """<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<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">
|
<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>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.7/ace.js"></script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
|
|
@ -412,6 +495,8 @@ button:hover{filter:brightness(1.1)}
|
||||||
.prof-btns { display: flex; gap: 8px; margin-top: 5px; }
|
.prof-btns { display: flex; gap: 8px; margin-top: 5px; }
|
||||||
.prof-btns button { flex: 1; }
|
.prof-btns button { flex: 1; }
|
||||||
|
|
||||||
|
.proxy-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
|
||||||
|
|
||||||
#last-load {
|
#last-load {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--txt);
|
color: var(--txt);
|
||||||
|
|
@ -469,7 +554,7 @@ button:hover{filter:brightness(1.1)}
|
||||||
<div class="hdr">
|
<div class="hdr">
|
||||||
<div style="display:flex;align-items:center;gap:10px">
|
<div style="display:flex;align-items:center;gap:10px">
|
||||||
<h2 style="margin:0;color:#4caf50">Mihomo Studio</h2>
|
<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>
|
||||||
<div id="last-load">Loaded: __TIME__</div>
|
<div id="last-load">Loaded: __TIME__</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -501,9 +586,12 @@ button:hover{filter:brightness(1.1)}
|
||||||
</div>
|
</div>
|
||||||
<div class="sec">
|
<div class="sec">
|
||||||
<h3>Управление прокси</h3>
|
<h3>Управление прокси</h3>
|
||||||
<button onclick="openAddProxyModal()" class="btn-s" style="width:100%">➕ Добавить прокси</button>
|
<div class="proxy-grid">
|
||||||
<button onclick="showDel()" class="btn-d" style="width:100%">🗑 Удалить прокси</button>
|
<button onclick="openAddProxyModal()" class="btn-s">➕ Добавить</button>
|
||||||
<button onclick="showRename()" class="btn-u" style="width:100%">✏️ Переименовать прокси</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>
|
||||||
<div class="sec">
|
<div class="sec">
|
||||||
<h3>Бэкапы</h3>
|
<h3>Бэкапы</h3>
|
||||||
|
|
@ -555,9 +643,16 @@ button:hover{filter:brightness(1.1)}
|
||||||
|
|
||||||
<div id="addProxyModal" class="ovl"><div class="mod">
|
<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;">
|
<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>
|
<button onclick="closeM('addProxyModal')" style="width:32px; height:32px; padding:0; background:var(--bg-ter); color:var(--txt); font-size:18px;">✕</button>
|
||||||
</div>
|
</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">
|
<div class="modal-tabs">
|
||||||
<button class="active" onclick="switchTab(event, 'vlessTab')">VLESS</button>
|
<button class="active" onclick="switchTab(event, 'vlessTab')">VLESS</button>
|
||||||
<button onclick="switchTab(event, 'wgTab')">WireGuard</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">
|
<div id="vlessTab" class="tab-content active">
|
||||||
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Ссылка VLESS:</label>
|
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Ссылка VLESS:</label>
|
||||||
<input id="vlessLink" placeholder="vless://..." style="margin-bottom:10px;">
|
<input id="vlessLink" placeholder="vless://..." style="margin-bottom:10px;">
|
||||||
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Имя прокси (необязательно):</label>
|
|
||||||
<input id="vlessProxyName" placeholder="Автоматически из ссылки" style="margin-bottom:10px;">
|
<div id="vless-name-block">
|
||||||
<button onclick="parseVless()" class="btn-s" style="width:100%; justify-content:center;">Добавить</button>
|
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Имя прокси (необязательно):</label>
|
||||||
|
<input id="vlessProxyName" placeholder="Автоматически из ссылки" style="margin-bottom:10px;">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button onclick="parseVless()" class="btn-s" style="width:100%; justify-content:center;">Сохранить</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="wgTab" class="tab-content">
|
<div id="wgTab" class="tab-content">
|
||||||
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Конфигурация WireGuard:</label>
|
<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>
|
<textarea id="wgConfig" rows="8" placeholder="Вставьте содержимое .conf файла сюда..." style="width:100%; margin-bottom:10px;"></textarea>
|
||||||
<label style="font-size:12px; margin-bottom:5px; color:var(--txt-sec)">Имя прокси (необязательно):</label>
|
|
||||||
<input id="wgProxyName" placeholder="Автоматически из Endpoint" style="margin-bottom:10px;">
|
<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)">
|
<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="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></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 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 pData=null, GRP_KEY="mihomo_grp_sel", LIM_KEY="mihomo_bk_lim", THM_KEY="mihomo_theme";
|
||||||
var initialConfig = __JSON_CONTENT__;
|
var initialConfig = __JSON_CONTENT__;
|
||||||
|
var isEditMode = false;
|
||||||
|
|
||||||
// Открываем панель через наш локальный прокси (безопасно для PNA/CORS)
|
// Открываем панель через наш локальный прокси (безопасно для PNA/CORS)
|
||||||
function openPanel() {
|
function openPanel() {
|
||||||
|
|
@ -604,6 +708,7 @@ function openPanel() {
|
||||||
ed.setValue(initialConfig); ed.clearSelection();
|
ed.setValue(initialConfig); ed.clearSelection();
|
||||||
|
|
||||||
document.getElementById('vlessLink').addEventListener('input', function() {
|
document.getElementById('vlessLink').addEventListener('input', function() {
|
||||||
|
if(isEditMode) return; // В режиме редактирования имя берется из селекта
|
||||||
var link = this.value;
|
var link = this.value;
|
||||||
if (link.startsWith("vless://") && link.includes("#")) {
|
if (link.startsWith("vless://") && link.includes("#")) {
|
||||||
var name = link.split('#')[1];
|
var name = link.split('#')[1];
|
||||||
|
|
@ -612,6 +717,7 @@ document.getElementById('vlessLink').addEventListener('input', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('wgConfig').addEventListener('input', function() {
|
document.getElementById('wgConfig').addEventListener('input', function() {
|
||||||
|
if(isEditMode) return;
|
||||||
var conf = this.value;
|
var conf = this.value;
|
||||||
var nameField = document.getElementById('wgProxyName');
|
var nameField = document.getElementById('wgProxyName');
|
||||||
var endpointMatch = conf.match(/Endpoint\s*=\s*(.+)/);
|
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() {
|
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';
|
document.getElementById('addProxyModal').style.display = 'flex';
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchTab(evt, tabName) {
|
function switchTab(evt, tabName) {
|
||||||
var i, tabcontent, tablinks;
|
var i, tabcontent, tablinks;
|
||||||
|
|
||||||
// Сначала скрываем все панели с контентом
|
|
||||||
tabcontent = document.getElementsByClassName("tab-content");
|
tabcontent = document.getElementsByClassName("tab-content");
|
||||||
for (i = 0; i < tabcontent.length; i++) {
|
for (i = 0; i < tabcontent.length; i++) {
|
||||||
tabcontent[i].classList.remove("active");
|
tabcontent[i].classList.remove("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Затем убираем активный класс со всех кнопок-вкладок
|
|
||||||
tablinks = document.getElementsByClassName("modal-tabs")[0].getElementsByTagName("button");
|
tablinks = document.getElementsByClassName("modal-tabs")[0].getElementsByTagName("button");
|
||||||
for (i = 0; i < tablinks.length; i++) {
|
for (i = 0; i < tablinks.length; i++) {
|
||||||
tablinks[i].classList.remove("active");
|
tablinks[i].classList.remove("active");
|
||||||
}
|
}
|
||||||
|
|
||||||
// И только потом показываем нужную вкладку и делаем ее активной
|
|
||||||
document.getElementById(tabName).classList.add("active");
|
document.getElementById(tabName).classList.add("active");
|
||||||
evt.currentTarget.classList.add("active");
|
evt.currentTarget.classList.add("active");
|
||||||
}
|
}
|
||||||
|
|
@ -820,7 +978,6 @@ function loadWgFile(input) {
|
||||||
r.onload = function(e) {
|
r.onload = function(e) {
|
||||||
var content = e.target.result;
|
var content = e.target.result;
|
||||||
document.getElementById('wgConfig').value = content;
|
document.getElementById('wgConfig').value = content;
|
||||||
// Trigger the input event to auto-fill the name
|
|
||||||
document.getElementById('wgConfig').dispatchEvent(new Event('input'));
|
document.getElementById('wgConfig').dispatchEvent(new Event('input'));
|
||||||
};
|
};
|
||||||
r.readAsText(f);
|
r.readAsText(f);
|
||||||
|
|
@ -829,53 +986,97 @@ function loadWgFile(input) {
|
||||||
|
|
||||||
function addWireguard() {
|
function addWireguard() {
|
||||||
var conf = document.getElementById('wgConfig').value;
|
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 не может быть пустой.");
|
if (!conf) return alert("Конфигурация WireGuard не может быть пустой.");
|
||||||
|
|
||||||
var p = new URLSearchParams();
|
var p = new URLSearchParams();
|
||||||
p.append('act', 'add_wireguard');
|
p.append('act', 'add_wireguard');
|
||||||
p.append('config_text', conf);
|
p.append('config_text', conf);
|
||||||
if (name) {
|
if (name) p.append('proxy_name', name);
|
||||||
p.append('proxy_name', name);
|
|
||||||
}
|
|
||||||
fetch('/', { method: 'POST', body: p })
|
fetch('/', { method: 'POST', body: p })
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(d => {
|
.then(d => {
|
||||||
if (d.error) {
|
if (d.error) {
|
||||||
alert(d.error);
|
alert(d.error);
|
||||||
} else {
|
} else {
|
||||||
pData = d;
|
if(isEditMode) {
|
||||||
closeM('addProxyModal');
|
replaceProxyData(name, d.yaml);
|
||||||
document.getElementById('wgConfig').value = '';
|
} else {
|
||||||
document.getElementById('wgProxyName').value = '';
|
pData = d;
|
||||||
showG();
|
closeM('addProxyModal');
|
||||||
|
showG();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseVless(){
|
function parseVless(){
|
||||||
var link = document.getElementById('vlessLink').value;
|
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;
|
if (!link) return;
|
||||||
|
|
||||||
var p = new URLSearchParams();
|
var p = new URLSearchParams();
|
||||||
p.append('act', 'parse');
|
p.append('act', 'parse');
|
||||||
p.append('link', link);
|
p.append('link', link);
|
||||||
if (name) {
|
if (name) p.append('proxy_name', name);
|
||||||
p.append('proxy_name', name);
|
|
||||||
}
|
|
||||||
fetch('/', { method: 'POST', body: p })
|
fetch('/', { method: 'POST', body: p })
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
.then(d => {
|
.then(d => {
|
||||||
if (d.error) {
|
if (d.error) {
|
||||||
alert(d.error);
|
alert(d.error);
|
||||||
} else {
|
} else {
|
||||||
pData = d;
|
if(isEditMode) {
|
||||||
closeM('addProxyModal');
|
replaceProxyData(name, d.yaml);
|
||||||
document.getElementById('vlessLink').value = '';
|
} else {
|
||||||
document.getElementById('vlessProxyName').value = '';
|
pData = d;
|
||||||
showG();
|
closeM('addProxyModal');
|
||||||
|
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(){
|
function showG(){
|
||||||
var txt=ed.getValue(); var ls=txt.split(/\\r?\\n/); var grps=[], inG=false;
|
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,''))}}
|
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("✅ Добавлено")}});
|
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(){
|
function showDel(){
|
||||||
var ls=ed.getValue().split(/\\r?\\n/); var prs=[], inP=0;
|
var prs = getProxiesList();
|
||||||
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 s=document.getElementById('sel-del');s.innerHTML='';
|
var s=document.getElementById('sel-del');s.innerHTML='';
|
||||||
prs.forEach(p=>{var o=document.createElement('option');o.text=p;s.add(o)});
|
prs.forEach(p=>{var o=document.createElement('option');o.text=p;s.add(o)});
|
||||||
document.getElementById('m-del').style.display='flex';
|
document.getElementById('m-del').style.display='flex';
|
||||||
|
|
@ -939,16 +1139,7 @@ function doDel(){
|
||||||
}
|
}
|
||||||
|
|
||||||
function showRename() {
|
function showRename() {
|
||||||
var ls = ed.getValue().split(/\\r?\\n/);
|
var prs = getProxiesList();
|
||||||
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 s = document.getElementById('sel-ren-proxy');
|
var s = document.getElementById('sel-ren-proxy');
|
||||||
s.innerHTML = '';
|
s.innerHTML = '';
|
||||||
prs.forEach(p => {
|
prs.forEach(p => {
|
||||||
|
|
@ -1185,22 +1376,15 @@ class H(http.server.SimpleHTTPRequestHandler):
|
||||||
return
|
return
|
||||||
|
|
||||||
# 1. Замена в определении прокси: - name: "old_name"
|
# 1. Замена в определении прокси: - name: "old_name"
|
||||||
# Regex для поиска `name: 'old_name'`, `name: "old_name"` или `name: old_name`
|
|
||||||
# Используем `re.escape` для безопасности
|
|
||||||
escaped_old = re.escape(old_name)
|
escaped_old = re.escape(old_name)
|
||||||
# (?P<quote>['"]?) - захватывает кавычку (если она есть) в группу 'quote'
|
|
||||||
# \\1 - ссылается на захваченную кавычку, чтобы заменить на такую же
|
|
||||||
pattern_def = r"(name\s*:\s*)(?P<quote>['\"]?)" + escaped_old + r"(?P=quote)"
|
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)
|
content = re.sub(pattern_def, r'\g<1>"' + new_name + '"', content, count=1)
|
||||||
|
|
||||||
# 2. Замена в списках proxy-groups: - "old_name" (Block Style)
|
# 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)"
|
pattern_list = r"(-\s+)(?P<quote>['\"]?)" + escaped_old + r"(?P=quote)"
|
||||||
content = re.sub(pattern_list, r'\g<1>"' + new_name + '"', content)
|
content = re.sub(pattern_list, r'\g<1>"' + new_name + '"', content)
|
||||||
|
|
||||||
# 3. Замена в Inline Lists: [ ..., "old_name", ... ]
|
# 3. Замена в Inline Lists: [ ..., "old_name", ... ]
|
||||||
# Ищем old_name внутри delimiters [ или , с последующим , или ]
|
|
||||||
pattern_inline = r"([\[,]\s*)(?P<q>['\"]?)" + escaped_old + r"(?P=q)(\s*[,\]])"
|
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)
|
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'));
|
s.wfile.write(json.dumps({'new_content': uc}).encode('utf-8'));
|
||||||
return
|
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':
|
if a == 'clean_backups':
|
||||||
limit = int(p.get('limit', 5))
|
limit = int(p.get('limit', 5))
|
||||||
files = sorted(glob.glob(BACKUP_DIR + "/*.yaml"), key=os.path.getmtime, reverse=True)
|
files = sorted(glob.glob(BACKUP_DIR + "/*.yaml"), key=os.path.getmtime, reverse=True)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue