This commit is contained in:
Zengtudor 2025-05-15 19:30:28 +08:00
parent 6d1fb82a35
commit bdbd170acf
2 changed files with 402 additions and 44 deletions

View File

@ -1,5 +1,5 @@
#pragma once #pragma once
constexpr const char* html = R"( constexpr const inline char* html = R"(
@HTML_CONTENT@ @HTML_CONTENT@
)"; )";

View File

@ -5,61 +5,334 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Environment Variables Viewer</title> <title>Environment Variables Viewer</title>
<style> <style>
body { font-family: system-ui, -apple-system, sans-serif; margin: 0; background: #f5f7fa; } :root {
.container { max-width: 1200px; margin: 0 auto; padding: 0 16px; } --primary-color: #165DFF;
header { background: white; padding: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05); } --primary-hover: #0E42CC;
h1 { color: #165DFF; margin: 0; } --primary-active: #0A3199;
main { padding: 24px 0; } --neutral-light: #F5F7FA;
.card { background: white; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); padding: 16px; } --neutral-medium: #E5E7EB;
table { width: 100%; border-collapse: collapse; } --neutral-dark: #6B7280;
th { text-align: left; padding: 12px 16px; background: #f9fafb; color: #6b7280; font-size: 12px; text-transform: uppercase; } --text-primary: #1D2129;
td { padding: 12px 16px; border-bottom: 1px solid #e5e7eb; } --text-secondary: #4E5969;
tr:last-child td { border-bottom: none; } --success: #36D399;
tr:nth-child(even) { background: #f9fafb; } --error: #EF4444;
tr:hover { background: #f3f4f6; } --warning: #FBBF24;
footer { background: #1d2129; color: white; text-align: center; padding: 20px 0; } --info: #3B82F6;
.loading { text-align: center; color: #9ca3af; padding: 20px 0; } --transition: all 0.3s ease;
.error { text-align: center; color: #ef4444; padding: 20px 0; } }
a { color: #165DFF; text-decoration: none; }
a:hover { text-decoration: underline; } * {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body {
background: var(--neutral-light);
color: var(--text-primary);
line-height: 1.6;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
header {
background: white;
padding: 1.5rem 0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05);
margin-bottom: 1.5rem;
position: sticky;
top: 0;
z-index: 10;
}
header .container {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
h1 {
color: var(--primary-color);
font-size: 1.75rem;
font-weight: 600;
}
header p {
color: var(--neutral-dark);
font-size: 0.9rem;
}
main {
padding-bottom: 3rem;
}
.card {
background: white;
border-radius: 0.75rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.03);
padding: 1.5rem;
margin-bottom: 1.5rem;
transition: var(--transition);
}
.card:hover {
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.07), 0 10px 10px -5px rgba(0, 0, 0, 0.02);
}
.btn { .btn {
background-color: #165DFF; background-color: var(--primary-color);
color: white; color: white;
border: none; border: none;
padding: 8px 16px; padding: 0.5rem 1rem;
border-radius: 4px; border-radius: 0.375rem;
cursor: pointer; cursor: pointer;
font-size: 14px; font-size: 0.9rem;
margin-bottom: 16px; font-weight: 500;
display: inline-flex;
align-items: center;
gap: 0.5rem;
transition: var(--transition);
box-shadow: 0 4px 6px -1px rgba(22, 93, 255, 0.1), 0 2px 4px -1px rgba(22, 93, 255, 0.06);
} }
.btn:hover { .btn:hover {
background-color: #0E42CC; background-color: var(--primary-hover);
transform: translateY(-1px);
box-shadow: 0 10px 15px -3px rgba(22, 93, 255, 0.15), 0 4px 6px -2px rgba(22, 93, 255, 0.08);
} }
.btn:active { .btn:active {
background-color: #0A3199; background-color: var(--primary-active);
transform: translateY(0);
box-shadow: 0 2px 4px -1px rgba(22, 93, 255, 0.1);
} }
.btn::before {
content: "↻";
display: inline-block;
animation: none;
}
.btn.loading::before {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
table {
width: 100%;
border-collapse: collapse;
}
th {
text-align: left;
padding: 0.75rem 1rem;
background: var(--neutral-light);
color: var(--neutral-dark);
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 500;
border-bottom: 1px solid var(--neutral-medium);
}
td {
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--neutral-medium);
color: var(--text-secondary);
}
tr:last-child td {
border-bottom: none;
}
tr:nth-child(even) {
background: var(--neutral-light);
}
tr {
transition: var(--transition);
}
tr:hover {
background: rgba(22, 93, 255, 0.05);
}
footer {
background: var(--text-primary);
color: white;
text-align: center;
padding: 1.5rem 0;
font-size: 0.875rem;
}
footer a {
color: white;
text-decoration: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
transition: var(--transition);
}
footer a:hover {
border-bottom-color: white;
}
.loading {
text-align: center;
color: var(--neutral-dark);
padding: 2rem 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.loading::before {
content: "";
width: 1.5rem;
height: 1.5rem;
border: 2px solid var(--neutral-medium);
border-radius: 50%;
border-top-color: var(--primary-color);
animation: spin 1s linear infinite;
margin-bottom: 0.75rem;
}
.error {
text-align: center;
color: var(--error);
padding: 2rem 0;
}
.scrollable { .scrollable {
max-height: 80px; max-height: 5rem;
overflow-y: auto; overflow-y: auto;
max-width: 300px; max-width: 100%;
border: 1px solid #e5e7eb; border: 1px solid var(--neutral-medium);
padding: 4px; padding: 0.5rem;
border-radius: 2px; border-radius: 0.25rem;
background-color: #f9fafb; background-color: var(--neutral-light);
font-family: monospace;
font-size: 0.875rem;
white-space: pre-wrap;
word-break: break-all;
} }
/* 自定义滚动条样式 */
/* 自定义滚动条 */
.scrollable::-webkit-scrollbar { .scrollable::-webkit-scrollbar {
width: 6px; width: 0.375rem;
} }
.scrollable::-webkit-scrollbar-track { .scrollable::-webkit-scrollbar-track {
background: #f1f1f1; background: var(--neutral-light);
border-radius: 0.1875rem;
} }
.scrollable::-webkit-scrollbar-thumb { .scrollable::-webkit-scrollbar-thumb {
background: #c5c5c5; background: var(--neutral-medium);
border-radius: 3px; border-radius: 0.1875rem;
} }
.scrollable::-webkit-scrollbar-thumb:hover { .scrollable::-webkit-scrollbar-thumb:hover {
background: #a8a8a8; background: var(--neutral-dark);
}
.fade-in {
animation: fadeIn 0.5s ease-in-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.env-key {
font-weight: 500;
}
.env-value {
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
font-size: 0.875rem;
}
/* 响应式设计 */
@media (max-width: 640px) {
.container {
padding: 0 0.75rem;
}
header {
padding: 1rem 0;
}
h1 {
font-size: 1.5rem;
}
.card {
padding: 1rem;
}
th, td {
padding: 0.5rem 0.75rem;
}
.scrollable {
max-height: 3.5rem;
}
}
.copy-btn {
position: absolute;
top: 0.25rem;
right: 0.25rem;
background: rgba(22, 93, 255, 0.1);
color: var(--primary-color);
border: none;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
cursor: pointer;
opacity: 0;
transition: var(--transition);
}
.copy-btn:hover {
background: rgba(22, 93, 255, 0.2);
}
.value-container {
position: relative;
}
.value-container:hover .copy-btn {
opacity: 1;
}
.copy-success {
position: absolute;
top: 0.25rem;
right: 0.25rem;
background: var(--success);
color: white;
border-radius: 0.25rem;
padding: 0.25rem 0.5rem;
font-size: 0.75rem;
opacity: 0;
transition: var(--transition);
pointer-events: none;
}
.copy-success.show {
opacity: 1;
} }
</style> </style>
</head> </head>
@ -73,7 +346,7 @@
<main class="container"> <main class="container">
<button id="refreshBtn" class="btn"> <button id="refreshBtn" class="btn">
<i class="fa fa-refresh mr-1"></i> Refresh Refresh
</button> </button>
<div class="card"> <div class="card">
<table id="envTable"> <table id="envTable">
@ -104,40 +377,126 @@
</footer> </footer>
<script> <script>
// 禁用右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
async function refreshEnvTable() { async function refreshEnvTable() {
const refreshBtn = document.getElementById('refreshBtn');
const tableBody = document.getElementById('envTableBody'); const tableBody = document.getElementById('envTableBody');
tableBody.innerHTML = '<tr><td colspan="2" class="loading">Refreshing environment variables...</td></tr>';
// 显示加载状态
tableBody.innerHTML = '<tr><td colspan="2" class="loading">Refreshing environment variables...</td></tr>';
refreshBtn.classList.add('loading');
refreshBtn.disabled = true;
try { try {
const env = await window.getEnvString(); const env = await window.getEnvString();
// 清空表格
tableBody.innerHTML = ''; tableBody.innerHTML = '';
// 检查是否有环境变量
if (!env || Object.keys(env).length === 0) { if (!env || Object.keys(env).length === 0) {
tableBody.innerHTML = '<tr><td colspan="2" class="loading">No environment variables found</td></tr>'; tableBody.innerHTML = '<tr><td colspan="2" class="loading">No environment variables found</td></tr>';
return; return;
} }
// 添加环境变量行
Object.entries(env).forEach(([key, value]) => { Object.entries(env).forEach(([key, value]) => {
const row = document.createElement('tr'); const row = document.createElement('tr');
row.className = 'fade-in';
row.innerHTML = ` row.innerHTML = `
<td>${key}</td> <td class="env-key">${key}</td>
<td><div class="scrollable" title="${value}">${value}</div></td> <td>
<div class="value-container">
<div class="scrollable env-value" title="${value}">${value}</div>
<button class="copy-btn" data-value="${value}">Copy</button>
<div class="copy-success">Copied!</div>
</div>
</td>
`; `;
tableBody.appendChild(row); tableBody.appendChild(row);
}); });
// 添加复制功能
document.querySelectorAll('.copy-btn').forEach(btn => {
btn.addEventListener('click', function() {
const value = this.getAttribute('data-value');
// 改进的复制功能,增加兼容性处理
if (navigator.clipboard) {
navigator.clipboard.writeText(value).then(() => {
showCopySuccess(this.nextElementSibling);
}).catch(err => {
console.error('Failed to copy using Clipboard API: ', err);
fallbackCopyTextToClipboard(value, this.nextElementSibling);
});
} else {
fallbackCopyTextToClipboard(value, this.nextElementSibling);
}
});
});
} catch (error) { } catch (error) {
tableBody.innerHTML = ` tableBody.innerHTML = `
<tr> <tr>
<td colspan="2" class="error">Failed to load: ${error.message}</td> <td colspan="2" class="error">Failed to load: ${error.message}</td>
</tr> </tr>
`; `;
} finally {
// 恢复按钮状态
refreshBtn.classList.remove('loading');
refreshBtn.disabled = false;
} }
} }
// 显示复制成功消息
function showCopySuccess(successMsg) {
successMsg.classList.add('show');
setTimeout(() => {
successMsg.classList.remove('show');
}, 3000);
}
// 备选复制方法
function fallbackCopyTextToClipboard(text, successMsg) {
const textArea = document.createElement("textarea");
textArea.value = text;
// 使 textarea 不在视窗内,防止影响布局
textArea.style.position = "fixed";
textArea.style.top = "-9999px";
textArea.style.left = "-9999px";
document.body.appendChild(textArea);
// 选中并复制文本
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful) {
showCopySuccess(successMsg);
} else {
console.error('Fallback: Copying text command failed');
}
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
// 移除 textarea
document.body.removeChild(textArea);
}
// 页面加载完成后刷新环境变量
document.addEventListener('DOMContentLoaded', refreshEnvTable); document.addEventListener('DOMContentLoaded', refreshEnvTable);
// 点击刷新按钮
document.getElementById('refreshBtn').addEventListener('click', refreshEnvTable); document.getElementById('refreshBtn').addEventListener('click', refreshEnvTable);
// Mock data for demonstration // 模拟数据
if (!window.getEnvString) { if (!window.getEnvString) {
window.getEnvString = async () => { window.getEnvString = async () => {
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
@ -155,5 +514,4 @@
} }
</script> </script>
</body> </body>
</html> </html>