feat: add password-gated config pages

This commit is contained in:
jocayn
2026-06-13 16:57:43 +08:00
parent 43728ec30e
commit c9d3665399
6 changed files with 891 additions and 22 deletions
+40 -6
View File
@@ -765,13 +765,14 @@
<th>生成时间</th>
<th>节点数量</th>
<th>本地下载</th>
<th>复制链接</th>
<th>一键导入</th>
<th>操作</th>
</tr>
</thead>
<tbody id="history-list">
<tr>
<td colspan="5" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
<td colspan="6" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
暂无生成历史记录,运行生成配置后将在此归档
</td>
</tr>
@@ -1457,6 +1458,27 @@
return `${window.location.protocol}//127.0.0.1${port}`;
}
async function copyLinkToClipboard(link) {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(link);
} else {
const textarea = document.createElement('textarea');
textarea.value = link;
textarea.setAttribute('readonly', '');
textarea.style.position = 'fixed';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
showToast('链接已复制');
} catch (e) {
showToast('复制链接失败', 'error');
}
}
function renderHistoryList(list) {
const tbody = document.getElementById('history-list');
tbody.innerHTML = '';
@@ -1464,7 +1486,7 @@
if (!list || list.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="5" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
<td colspan="6" style="text-align: center; color: var(--text-secondary); padding: 3rem;">
暂无生成历史记录,运行生成配置后将在此归档
</td>
</tr>
@@ -1478,10 +1500,12 @@
list.forEach(item => {
const yamlFileParam = encodeURIComponent(item.yamlFile);
const jsonFileParam = encodeURIComponent(item.jsonFile);
const yamlDownloadUrl = `${origin}/api/download?file=${yamlFileParam}`;
const jsonDownloadUrl = `${origin}/api/download?file=${jsonFileParam}`;
const yamlImportProfileUrl = `${clientOrigin}/api/profile?file=${yamlFileParam}`;
const jsonImportProfileUrl = `${clientOrigin}/api/profile?file=${jsonFileParam}`;
const yamlTokenParam = item.yamlToken ? `&token=${encodeURIComponent(item.yamlToken)}` : '';
const jsonTokenParam = item.jsonToken ? `&token=${encodeURIComponent(item.jsonToken)}` : '';
const yamlDownloadUrl = `${origin}/api/download?file=${yamlFileParam}${yamlTokenParam}`;
const jsonDownloadUrl = `${origin}/api/download?file=${jsonFileParam}${jsonTokenParam}`;
const yamlImportProfileUrl = `${clientOrigin}/api/profile?file=${yamlFileParam}${yamlTokenParam}`;
const jsonImportProfileUrl = `${clientOrigin}/api/profile?file=${jsonFileParam}${jsonTokenParam}`;
// Construct client URI schemes
const clashImportUrl = `clash://install-config?url=${encodeURIComponent(yamlImportProfileUrl)}&name=${encodeURIComponent('CatMata-' + item.timeStr.replace(/[: ]/g, '-'))}`;
@@ -1501,6 +1525,16 @@
</a>
</div>
</td>
<td>
<div style="display: flex; gap: 0.5rem;">
<button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;" onclick="copyLinkToClipboard('${yamlImportProfileUrl}')">
<i data-lucide="copy" style="width: 12px; height: 12px;"></i>YAML
</button>
<button type="button" class="btn btn-secondary" style="padding: 0.25rem 0.5rem; font-size: 0.75rem;" onclick="copyLinkToClipboard('${jsonImportProfileUrl}')">
<i data-lucide="copy" style="width: 12px; height: 12px;"></i>JSON
</button>
</div>
</td>
<td>
<div style="display: flex; gap: 0.5rem;">
<a class="btn btn-primary" style="padding: 0.25rem 0.5rem; font-size: 0.75rem; text-decoration: none; background: linear-gradient(135deg, #10B981 0%, #059669 100%); box-shadow: 0 4px 10px rgba(16, 185, 129, 0.2);" href="${clashImportUrl}">