data&device ready
This commit is contained in:
313
scripts/privacy.js
Normal file
313
scripts/privacy.js
Normal file
@@ -0,0 +1,313 @@
|
||||
/**
|
||||
* TalkingQ Platform - Privacy (PII) Page
|
||||
*/
|
||||
|
||||
registerPage('privacy', function(container) {
|
||||
container.innerHTML = `
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<div class="page-title">隐私保护 (PII)</div>
|
||||
<div class="page-desc">个人信息脱敏、COPPA/GDPR 合规配置与数据保留策略</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" id="privacyTabs">
|
||||
<div class="tab-item active" data-tab="pii">PII 脱敏规则</div>
|
||||
<div class="tab-item" data-tab="gdpr">GDPR/COPPA</div>
|
||||
<div class="tab-item" data-tab="retention">数据保留</div>
|
||||
</div>
|
||||
|
||||
<!-- PII Tab -->
|
||||
<div class="tab-content active" id="tab-pii">
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<div class="card-header">
|
||||
<div>
|
||||
<div class="card-title">PII 自动脱敏配置</div>
|
||||
<div class="card-subtitle">自动识别并脱敏对话中的个人敏感信息</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:8px">
|
||||
<span style="font-size:13px;color:var(--text-muted)">全局启用</span>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" checked>
|
||||
<span class="toggle-track"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:12px">
|
||||
${[
|
||||
{ name: '姓名', example: '张三 → ***', enabled: true },
|
||||
{ name: '手机号', example: '138****8888', enabled: true },
|
||||
{ name: 'IP 地址', example: '192.168.x.x', enabled: true },
|
||||
{ name: '身份证号', example: '330102****1234', enabled: true },
|
||||
{ name: '家庭住址', example: '北京市朝阳区*** → [地址已脱敏]', enabled: true },
|
||||
{ name: '银行卡号', example: '6222****1234', enabled: false },
|
||||
].map(r => `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border:1px solid var(--border);border-radius:var(--radius-sm)">
|
||||
<div>
|
||||
<div style="font-weight:500;font-size:13px">${r.name}</div>
|
||||
<div style="font-size:12px;color:var(--text-muted);font-family:monospace">${r.example}</div>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" ${r.enabled ? 'checked' : ''}>
|
||||
<span class="toggle-track"></span>
|
||||
</label>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="api-note">
|
||||
<i data-lucide="info"></i>
|
||||
<div>PII 规则接口:<code>GET /api/v1/privacy/pii-rules</code> · <code>PUT /api/v1/privacy/pii-rules/{id}</code>。
|
||||
脱敏事件通过 <code>pii_detected</code> / <code>pii_masked</code> 事件上报。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GDPR Tab -->
|
||||
<div class="tab-content" id="tab-gdpr">
|
||||
<div style="display:grid;grid-template-columns:repeat(2,1fr);gap:16px">
|
||||
<div class="card">
|
||||
<div class="card-title" style="margin-bottom:12px">COPPA 合规(儿童隐私保护)</div>
|
||||
${[
|
||||
{ label: '用户年龄验证', enabled: true },
|
||||
{ label: '家长知情同意收集', enabled: true },
|
||||
{ label: '禁止收集13岁以下精确位置', enabled: true },
|
||||
{ label: '行为追踪限制', enabled: true },
|
||||
{ label: '数据最小化原则', enabled: true },
|
||||
].map(i => `
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
|
||||
<span style="font-size:13px">${i.label}</span>
|
||||
<label class="toggle-switch"><input type="checkbox" ${i.enabled ? 'checked' : ''}><span class="toggle-track"></span></label>
|
||||
</div>
|
||||
`).join('')}
|
||||
<div style="margin-top:12px;padding:10px;background:var(--primary-bg);border-radius:var(--radius-sm);font-size:12px;color:var(--primary)">
|
||||
✅ COPPA 合规状态:已合规
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-title" style="margin-bottom:12px">GDPR 数据权利</div>
|
||||
${[
|
||||
{ label: '知情权(提供隐私政策)', enabled: true },
|
||||
{ label: '访问权(数据导出)', enabled: true },
|
||||
{ label: '更正权', enabled: true },
|
||||
{ label: '删除权(被遗忘权)', enabled: true },
|
||||
{ label: '数据可携带权', enabled: false },
|
||||
].map(i => `
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
|
||||
<span style="font-size:13px">${i.label}</span>
|
||||
<label class="toggle-switch"><input type="checkbox" ${i.enabled ? 'checked' : ''}><span class="toggle-track"></span></label>
|
||||
</div>
|
||||
`).join('')}
|
||||
<div style="margin-top:12px;padding:10px;background:#fffbe6;border-radius:var(--radius-sm);font-size:12px;color:var(--warning)">
|
||||
⚠️ 数据可携带权尚未完整实现
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Retention Tab -->
|
||||
<div class="tab-content" id="tab-retention">
|
||||
<div class="card">
|
||||
<div class="card-title" style="margin-bottom:16px">数据保留策略</div>
|
||||
${[
|
||||
{ type: '对话记录 (Sessions)', period: '180', unit: '天' },
|
||||
{ type: '设备日志 (Device Logs)', period: '90', unit: '天' },
|
||||
{ type: 'Bad Case 数据', period: '365', unit: '天' },
|
||||
{ type: 'OTA 升级记录', period: '365', unit: '天' },
|
||||
{ type: '审计日志 (Audit Logs)', period: '730', unit: '天' },
|
||||
{ type: 'PII 脱敏日志', period: '30', unit: '天' },
|
||||
].map(p => `
|
||||
<div style="display:flex;align-items:center;gap:16px;padding:10px 0;border-bottom:1px solid var(--border)">
|
||||
<span style="flex:1;font-size:13px">${p.type}</span>
|
||||
<input type="number" class="form-control" value="${p.period}" style="width:80px">
|
||||
<span style="font-size:13px;color:var(--text-muted)">${p.unit}</span>
|
||||
</div>
|
||||
`).join('')}
|
||||
<button class="btn btn-primary" style="margin-top:16px" onclick="Toast.success('数据保留策略已保存')">
|
||||
<i data-lucide="save"></i> 保存策略
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
lucide.createIcons({ elements: [container] });
|
||||
|
||||
container.querySelectorAll('#privacyTabs .tab-item').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
container.querySelectorAll('#privacyTabs .tab-item').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
container.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||
container.querySelector(`#tab-${tab.dataset.tab}`)?.classList.add('active');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* TalkingQ Platform - Settings Page
|
||||
*/
|
||||
registerPage('settings', function(container) {
|
||||
container.innerHTML = `
|
||||
<div class="page-header">
|
||||
<div class="page-title">系统设置</div>
|
||||
<div class="page-desc">租户信息、API Key 管理、通知配置与集成</div>
|
||||
</div>
|
||||
|
||||
<div class="tabs" id="settingsTabs">
|
||||
<div class="tab-item active" data-tab="tenant">租户信息</div>
|
||||
<div class="tab-item" data-tab="apikeys">API Keys</div>
|
||||
<div class="tab-item" data-tab="notify">通知配置</div>
|
||||
<div class="tab-item" data-tab="integrations">集成</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content active" id="stab-tenant">
|
||||
<div class="card" style="max-width:600px">
|
||||
<div class="card-title" style="margin-bottom:20px">租户基本信息</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">租户名称</label>
|
||||
<input type="text" class="form-control" value="TalkingQ 默认租户">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">租户 ID</label>
|
||||
<input type="text" class="form-control" value="tenant-20240501" readonly style="background:#fafafa;color:var(--text-muted)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">联系邮箱</label>
|
||||
<input type="email" class="form-control" value="admin@talkingq.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">时区</label>
|
||||
<select class="form-control">
|
||||
<option selected>Asia/Shanghai (UTC+8)</option>
|
||||
<option>UTC</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="Toast.success('租户信息已保存')">
|
||||
<i data-lucide="save"></i> 保存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="stab-apikeys">
|
||||
<div class="card">
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:16px">
|
||||
<div class="card-title">API Keys</div>
|
||||
<button class="btn btn-primary btn-sm" id="createKeyBtn">
|
||||
<i data-lucide="plus"></i> 创建 API Key
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead><tr><th>名称</th><th>Key(部分)</th><th>权限</th><th>创建时间</th><th>最后使用</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
${[
|
||||
{ name: 'Backend Service Key', key: 'tq_live_****a3f2', scope: '全部权限', created: '2026-01-01', used: '2分钟前' },
|
||||
{ name: 'Analytics Read-only', key: 'tq_ro_****c8e1', scope: '只读', created: '2026-03-15', used: '今天' },
|
||||
].map(k => `
|
||||
<tr>
|
||||
<td style="font-weight:500">${k.name}</td>
|
||||
<td style="font-family:monospace;font-size:12px">${k.key}</td>
|
||||
<td><span class="badge badge-info">${k.scope}</span></td>
|
||||
<td style="font-size:12px;color:var(--text-muted)">${k.created}</td>
|
||||
<td style="font-size:12px;color:var(--text-muted)">${k.used}</td>
|
||||
<td>
|
||||
<button class="btn btn-ghost btn-sm" data-tooltip="复制 Key" onclick="Toast.info('Key 已复制到剪贴板')"><i data-lucide="copy"></i></button>
|
||||
<button class="btn btn-ghost btn-sm" style="color:var(--danger)" data-tooltip="撤销 Key" onclick="Toast.warning('Key 已撤销')"><i data-lucide="trash-2"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="api-note" style="margin-top:12px">
|
||||
<i data-lucide="info"></i>
|
||||
<span>API Key 接口:<code>GET /api/v1/settings/api-keys</code> · <code>POST /api/v1/settings/api-keys</code></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="stab-notify">
|
||||
<div class="card" style="max-width:600px">
|
||||
<div class="card-title" style="margin-bottom:16px">通知配置</div>
|
||||
${[
|
||||
{ label: '设备离线告警', enabled: true },
|
||||
{ label: 'OTA 升级失败告警', enabled: true },
|
||||
{ label: 'Bad Case 严重问题告警', enabled: true },
|
||||
{ label: 'API 超时告警', enabled: false },
|
||||
{ label: '每日数据报告', enabled: true },
|
||||
].map(n => `
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--border)">
|
||||
<span style="font-size:13px">${n.label}</span>
|
||||
<label class="toggle-switch"><input type="checkbox" ${n.enabled ? 'checked' : ''}><span class="toggle-track"></span></label>
|
||||
</div>
|
||||
`).join('')}
|
||||
<div class="form-group" style="margin-top:16px">
|
||||
<label class="form-label">Webhook URL</label>
|
||||
<input type="text" class="form-control" placeholder="https://your-webhook.com/alerts">
|
||||
</div>
|
||||
<button class="btn btn-outline btn-sm" onclick="Toast.info('Webhook 测试请求已发送')">
|
||||
<i data-lucide="send"></i> 测试 Webhook
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-content" id="stab-integrations">
|
||||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">
|
||||
${[
|
||||
{ name: '腾讯云 ASR', icon: '🔵', status: 'connected' },
|
||||
{ name: '腾讯云 TTS', icon: '🔵', status: 'connected' },
|
||||
{ name: 'OpenAI', icon: '⚫', status: 'disconnected' },
|
||||
{ name: '阿里云 OSS', icon: '🟠', status: 'connected' },
|
||||
{ name: 'Kafka', icon: '🟡', status: 'connected' },
|
||||
{ name: 'ClickHouse', icon: '🔴', status: 'disconnected' },
|
||||
].map(i => `
|
||||
<div class="card" style="display:flex;align-items:center;gap:12px">
|
||||
<span style="font-size:24px">${i.icon}</span>
|
||||
<div style="flex:1">
|
||||
<div style="font-weight:600;font-size:13.5px">${i.name}</div>
|
||||
<span class="status-dot ${i.status === 'connected' ? 'online' : 'offline'}" style="font-size:12px">${i.status === 'connected' ? '已连接' : '未连接'}</span>
|
||||
</div>
|
||||
<button class="btn btn-outline btn-sm" onclick="Toast.info('配置 ${i.name} 集成')">配置</button>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
lucide.createIcons({ elements: [container] });
|
||||
|
||||
container.querySelectorAll('#settingsTabs .tab-item').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
container.querySelectorAll('#settingsTabs .tab-item').forEach(t => t.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
container.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
||||
container.querySelector(`#stab-${tab.dataset.tab}`)?.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
container.querySelector('#createKeyBtn')?.addEventListener('click', () => {
|
||||
createModal({
|
||||
title: '创建 API Key',
|
||||
content: `
|
||||
<div class="form-group">
|
||||
<label class="form-label">Key 名称<span class="required">*</span></label>
|
||||
<input type="text" class="form-control" placeholder="例:Backend Service Key">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">权限范围</label>
|
||||
<select class="form-control">
|
||||
<option>全部权限</option>
|
||||
<option>只读</option>
|
||||
<option>设备管理</option>
|
||||
<option>OTA 管理</option>
|
||||
</select>
|
||||
</div>`,
|
||||
onConfirm: async () => {
|
||||
await API.settings.createApiKey({ name: '新 API Key' });
|
||||
Toast.success('API Key 创建成功,请妥善保存');
|
||||
},
|
||||
confirmText: '创建',
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user