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
constexpr const char* html = R"(
constexpr const inline char* html = R"(
@HTML_CONTENT@
)";

View File

@ -5,61 +5,334 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Environment Variables Viewer</title>
<style>
body { font-family: system-ui, -apple-system, sans-serif; margin: 0; background: #f5f7fa; }
.container { max-width: 1200px; margin: 0 auto; padding: 0 16px; }
header { background: white; padding: 20px 0; box-shadow: 0 2px 10px rgba(0,0,0,0.05); }
h1 { color: #165DFF; margin: 0; }
main { padding: 24px 0; }
.card { background: white; border-radius: 8px; box-shadow: 0 2px 5px rgba(0,0,0,0.05); padding: 16px; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; padding: 12px 16px; background: #f9fafb; color: #6b7280; font-size: 12px; text-transform: uppercase; }
td { padding: 12px 16px; border-bottom: 1px solid #e5e7eb; }
tr:last-child td { border-bottom: none; }
tr:nth-child(even) { background: #f9fafb; }
tr:hover { background: #f3f4f6; }
footer { background: #1d2129; color: white; text-align: center; padding: 20px 0; }
.loading { text-align: center; color: #9ca3af; padding: 20px 0; }
.error { text-align: center; color: #ef4444; padding: 20px 0; }
a { color: #165DFF; text-decoration: none; }
a:hover { text-decoration: underline; }
:root {
--primary-color: #165DFF;
--primary-hover: #0E42CC;
--primary-active: #0A3199;
--neutral-light: #F5F7FA;
--neutral-medium: #E5E7EB;
--neutral-dark: #6B7280;
--text-primary: #1D2129;
--text-secondary: #4E5969;
--success: #36D399;
--error: #EF4444;
--warning: #FBBF24;
--info: #3B82F6;
--transition: all 0.3s ease;
}
* {
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 {
background-color: #165DFF;
background-color: var(--primary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
cursor: pointer;
font-size: 14px;
margin-bottom: 16px;
font-size: 0.9rem;
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 {
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 {
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 {
max-height: 80px;
max-height: 5rem;
overflow-y: auto;
max-width: 300px;
border: 1px solid #e5e7eb;
padding: 4px;
border-radius: 2px;
background-color: #f9fafb;
max-width: 100%;
border: 1px solid var(--neutral-medium);
padding: 0.5rem;
border-radius: 0.25rem;
background-color: var(--neutral-light);
font-family: monospace;
font-size: 0.875rem;
white-space: pre-wrap;
word-break: break-all;
}
/* 自定义滚动条样式 */
/* 自定义滚动条 */
.scrollable::-webkit-scrollbar {
width: 6px;
width: 0.375rem;
}
.scrollable::-webkit-scrollbar-track {
background: #f1f1f1;
background: var(--neutral-light);
border-radius: 0.1875rem;
}
.scrollable::-webkit-scrollbar-thumb {
background: #c5c5c5;
border-radius: 3px;
background: var(--neutral-medium);
border-radius: 0.1875rem;
}
.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>
</head>
@ -73,7 +346,7 @@
<main class="container">
<button id="refreshBtn" class="btn">
<i class="fa fa-refresh mr-1"></i> Refresh
Refresh
</button>
<div class="card">
<table id="envTable">
@ -104,40 +377,126 @@
</footer>
<script>
// 禁用右键菜单
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
async function refreshEnvTable() {
const refreshBtn = document.getElementById('refreshBtn');
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 {
const env = await window.getEnvString();
// 清空表格
tableBody.innerHTML = '';
// 检查是否有环境变量
if (!env || Object.keys(env).length === 0) {
tableBody.innerHTML = '<tr><td colspan="2" class="loading">No environment variables found</td></tr>';
return;
}
// 添加环境变量行
Object.entries(env).forEach(([key, value]) => {
const row = document.createElement('tr');
row.className = 'fade-in';
row.innerHTML = `
<td>${key}</td>
<td><div class="scrollable" title="${value}">${value}</div></td>
<td class="env-key">${key}</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);
});
// 添加复制功能
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) {
tableBody.innerHTML = `
<tr>
<td colspan="2" class="error">Failed to load: ${error.message}</td>
</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.getElementById('refreshBtn').addEventListener('click', refreshEnvTable);
// Mock data for demonstration
// 模拟数据
if (!window.getEnvString) {
window.getEnvString = async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
@ -155,5 +514,4 @@
}
</script>
</body>
</html>
</html>