feat: implement Phase 8 + Phase 9 (S2/S3/S4 deployment approval + system autonomy)

Phase 8 - Dual-Environment Deploy Flow + SkyEye Final Review Authorization:
- Add backend/api-server/routes/approval.js: request/decide/status/list endpoints
- Add backend/api-server/config/approvers.json: 肥猫(DEV-002) + 桔子(DEV-010) config
- Add docs/js/approval-ui.js: frontend approval badge + panel + decision UI
- Add docs/css/approval.css: approval panel styles
- Update permissions.js: add approval:decide, approval:create, system:internal
- Update server.js to v4.0.0: register approval routes

Phase 9 - System Autonomy Framework:
- Add backend/api-server/config/autonomy-rules.json: dual-line architecture + deploy stages
- Add backend/api-server/services/autonomy-engine.js: compliance check + mode detection

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/6ee737eb-684b-44b8-aeaa-f73eb7c6f9c2
This commit is contained in:
copilot-swe-agent[bot] 2026-03-25 08:48:50 +00:00
parent ade9c8dfc3
commit 721974ca76
8 changed files with 962 additions and 7 deletions

View File

@ -0,0 +1,41 @@
{
"version": "1.0.0",
"description": "天眼终审授权人配置 · 部署到正式站前的人类终审",
"rules": {
"男频": {
"approvers": [
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" }
],
"requireAll": true
},
"女频": {
"approvers": [
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
],
"requireAll": true
},
"系统": {
"approvers": [
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" },
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
],
"requireAll": true
},
"跨频": {
"approvers": [
{ "devId": "DEV-002", "name": "肥猫", "role": "男频内容授权人" },
{ "devId": "DEV-010", "name": "桔子", "role": "女频内容授权人" }
],
"requireAll": true
}
},
"defaults": {
"channel": "系统",
"timeoutHours": 72,
"noResponseAction": "不通过(不回应视为不通过)"
},
"audit": {
"logEveryDecision": true,
"fields": ["timestamp", "approvalId", "devId", "decision", "channel", "module"]
}
}

View File

@ -0,0 +1,75 @@
{
"version": "1.0.0",
"description": "系统自治运行规则 · Phase 9",
"dualLineArchitecture": {
"lineA": {
"name": "操作系统底座线",
"owner": "TCS-0002",
"scope": ["权限系统", "部署流水线", "神经网络升级", "执行保护层", "架构变更"],
"interactionModes": {
"instructionMode": {
"description": "冰朔说方向 → 霜砚拆指令 → 铸渊执行",
"trigger": ["签发指令", "写个指令", "下发指令"],
"suitableFor": "架构级、系统级变更"
},
"naturalLanguageMode": {
"description": "冰朔直接和系统说话,系统即时响应",
"trigger": "默认模式(直接说需求/问问题)",
"suitableFor": "日常操作、快速调整"
}
},
"priority": "最高 · 冰朔说话 = 最高优先级,无论哪种模式"
},
"lineB": {
"name": "具体功能线",
"owners": "开发者团队",
"scope": ["Persona Studio", "工单系统", "数据看板", "动态漫画", "各模块功能"],
"interactionMode": "日常交互(和铸渊说话 → 系统执行)"
}
},
"autonomyRules": {
"systemCanDo": [
"自主调整内部运行逻辑(不违反铁律)",
"自主升级能力模块(记录到审计日志)",
"自主优化瓶颈路径",
"定期生成系统健康报告",
"异常时主动向霜砚报告"
],
"systemMustNot": [
"违反语言系统两条铁律",
"绕过天眼审核",
"绕过人类授权步骤",
"修改权限分层(权限变更只有冰朔能触发)"
]
},
"deploymentFlow": {
"stages": [
{
"stage": 1,
"name": "部署到预览站",
"target": "GitHub Pages (qinfendebingshuo.github.io/guanghulab/docs/)",
"description": "真实可访问的预览环境,让人类确认执行结果"
},
{
"stage": 2,
"name": "天眼审核",
"checks": ["代码安全", "结构完整性", "功能回归"],
"passAction": "进入 Stage 3",
"failAction": "标记问题,开发者修复后重新提交"
},
{
"stage": 3,
"name": "人类终审授权",
"description": "授权人必须手动点击确认,不回应视为不通过",
"configFile": "approvers.json"
},
{
"stage": 4,
"name": "自动发布到正式站",
"target": "guanghulab.com",
"trigger": "deploy-to-server.yml",
"description": "授权即发布,无需人类再次操作"
}
]
}
}

View File

@ -44,9 +44,10 @@ var PERMISSION_LEVELS = {
'agent:read',
'repo:read',
'deploy:preview', 'deploy:production',
'maintenance:log'
'maintenance:log',
'approval:decide'
],
description: '可操作自己的模块,含正式站部署'
description: '可操作自己的模块,含正式站部署和授权决策'
},
3: {
name: '管理者',
@ -60,7 +61,9 @@ var PERMISSION_LEVELS = {
'deploy:preview', 'deploy:production',
'broadcast:create',
'maintenance:log',
'permission:manage'
'permission:manage',
'approval:decide', 'approval:create',
'system:internal'
],
description: '系统管理员,全部权限'
}

View File

@ -0,0 +1,312 @@
/**
* 部署授权流程路由 · Phase 8
*
* 天眼审核通过后 推送授权请求给授权人 授权人确认/拒绝 自动发布
* 系统绝不自动跳过授权步骤没有人类点确认就不发布
*
* POST /api/approval/request 创建授权请求天眼/系统内部调用
* POST /api/approval/:id/decide 授权人确认/拒绝
* GET /api/approval/:id 查询单个授权状态
* GET /api/approval/pending 查询待处理的授权列表
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var fs = require('fs');
var path = require('path');
var authMiddleware = require('../middleware/auth');
var auditMiddleware = require('../middleware/audit');
var approversConfig = require('../config/approvers.json');
// ====== 内存中的授权记录(生产环境可迁移到持久化存储)======
var approvalStore = new Map();
// ====== 辅助函数 ======
/**
* 根据频道获取授权人列表
*/
function getApprovers(channel) {
var rule = approversConfig.rules[channel] || approversConfig.rules[approversConfig.defaults.channel];
return rule ? rule.approvers : approversConfig.rules['系统'].approvers;
}
/**
* 写入审计日志授权专用
*/
function writeApprovalAudit(entry) {
var logDir = process.env.AUDIT_LOG_DIR || path.join(__dirname, '../../logs/audit');
try {
fs.mkdirSync(logDir, { recursive: true });
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(logDir, 'approval-' + today + '.jsonl');
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
if (err) console.error('授权审计日志写入失败:', err.message);
});
} catch (e) {
console.error('授权审计日志写入失败:', e.message);
}
}
// ====== 路由 ======
// 所有授权路由需要认证
router.use(authMiddleware.requireAuth);
router.use(auditMiddleware.auditLog);
/**
* 创建授权请求天眼审核通过后由系统调用
*/
router.post('/request', function(req, res) {
// 仅系统内部或管理员可创建授权请求
if (req.user.permissionLevel < 3 && req.user.devId !== 'SYSTEM') {
return res.status(403).json({
error: true,
code: 'PERMISSION_DENIED',
reply: '🔒 仅系统内部或管理员可创建授权请求。'
});
}
var body = req.body || {};
var deployId = body.deployId;
var module = body.module;
var channel = body.channel || '系统';
var reviewReport = body.reviewReport || {};
if (!deployId || !module) {
return res.status(400).json({
error: true,
code: 'MISSING_FIELDS',
reply: '❌ 缺少必要字段deployId, module'
});
}
var approvers = getApprovers(channel);
var approvalId = 'APPROVAL-' + Date.now() + '-' + Math.random().toString(36).substring(2, 6);
var approval = {
id: approvalId,
deployId: deployId,
module: module,
channel: channel,
reviewReport: reviewReport,
approvers: approvers.map(function(a) { return a.devId; }),
approverDetails: approvers,
decisions: {},
status: 'pending',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
approvalStore.set(approvalId, approval);
writeApprovalAudit({
action: 'approval_created',
approvalId: approvalId,
deployId: deployId,
module: module,
channel: channel,
approvers: approvers.map(function(a) { return a.devId; }),
createdBy: req.user.devId,
timestamp: new Date().toISOString()
});
res.json({
success: true,
approvalId: approvalId,
approvers: approvers,
reply: '📋 授权请求已创建(' + approvalId + ')。已推送给授权人:' +
approvers.map(function(a) { return a.name + '(' + a.devId + ')'; }).join('、') + '。'
});
});
/**
* 授权人确认/拒绝
*/
router.post('/:approvalId/decide', function(req, res) {
var approvalId = req.params.approvalId;
var decision = (req.body || {}).decision; // 'approved' | 'rejected'
var reason = (req.body || {}).reason || '';
var devId = req.user.devId;
var approval = approvalStore.get(approvalId);
if (!approval) {
return res.status(404).json({
error: true,
code: 'NOT_FOUND',
reply: '❌ 授权请求 ' + approvalId + ' 不存在。'
});
}
if (approval.status !== 'pending') {
return res.status(400).json({
error: true,
code: 'ALREADY_DECIDED',
reply: '❌ 该授权请求已处理完毕(状态:' + approval.status + ')。'
});
}
// 验证是否为授权人
if (!approval.approvers.includes(devId)) {
return res.status(403).json({
error: true,
code: 'NOT_APPROVER',
reply: '🔒 你不是这个部署的授权人。授权人:' +
approval.approverDetails.map(function(a) { return a.name; }).join('、')
});
}
if (decision !== 'approved' && decision !== 'rejected') {
return res.status(400).json({
error: true,
code: 'INVALID_DECISION',
reply: '❌ decision 必须是 "approved" 或 "rejected"。'
});
}
// 记录决定
approval.decisions[devId] = {
decision: decision,
reason: reason,
timestamp: new Date().toISOString()
};
approval.updatedAt = new Date().toISOString();
// 写入审计日志(不可逆)
writeApprovalAudit({
action: 'approval_decision',
approvalId: approvalId,
devId: devId,
decision: decision,
reason: reason,
module: approval.module,
channel: approval.channel,
timestamp: new Date().toISOString()
});
if (decision === 'rejected') {
approval.status = 'rejected';
return res.json({
success: true,
reply: '❌ 已拒绝。部署不会执行。原因已记录到审计日志。'
});
}
// 检查是否所有授权人都已通过
var allApproved = approval.approvers.every(function(id) {
return approval.decisions[id] && approval.decisions[id].decision === 'approved';
});
if (allApproved) {
approval.status = 'approved';
// 触发正式站部署
var githubService;
try {
githubService = require('../services/github');
} catch (_) {
githubService = null;
}
if (githubService && githubService.triggerWorkflow) {
githubService.triggerWorkflow('deploy-to-server.yml', {
module: approval.module,
deploy_id: approval.deployId,
approved_by: approval.approvers.join(','),
target: 'production'
}).then(function() {
writeApprovalAudit({
action: 'deploy_triggered',
approvalId: approvalId,
module: approval.module,
approvedBy: approval.approvers,
timestamp: new Date().toISOString()
});
}).catch(function(err) {
console.error('部署触发失败:', err.message);
writeApprovalAudit({
action: 'deploy_trigger_failed',
approvalId: approvalId,
error: err.message,
timestamp: new Date().toISOString()
});
});
}
return res.json({
success: true,
reply: '✅ 所有授权人已通过。铸渊已自动触发正式站guanghulab.com部署。'
});
}
// 还有授权人未决定
var pending = approval.approvers.filter(function(id) { return !approval.decisions[id]; });
res.json({
success: true,
reply: '✅ 你已授权通过。等待其他授权人确认:' + pending.join('、')
});
});
/**
* 查询单个授权状态
*/
router.get('/:approvalId', function(req, res) {
var approval = approvalStore.get(req.params.approvalId);
if (!approval) {
return res.status(404).json({
error: true,
code: 'NOT_FOUND',
reply: '❌ 授权请求不存在。'
});
}
res.json({
success: true,
approval: {
id: approval.id,
deployId: approval.deployId,
module: approval.module,
channel: approval.channel,
status: approval.status,
approvers: approval.approverDetails,
decisions: approval.decisions,
createdAt: approval.createdAt,
updatedAt: approval.updatedAt
}
});
});
/**
* 查询待处理的授权列表授权人查自己待处理的
*/
router.get('/', function(req, res) {
var devId = req.user.devId;
var pending = [];
for (var entry of approvalStore) {
var approval = entry[1];
if (approval.status === 'pending' && approval.approvers.includes(devId) && !approval.decisions[devId]) {
pending.push({
id: approval.id,
module: approval.module,
channel: approval.channel,
createdAt: approval.createdAt
});
}
}
res.json({
success: true,
pending: pending,
count: pending.length,
reply: pending.length > 0
? '📋 你有 ' + pending.length + ' 个待授权请求。'
: '✅ 没有待处理的授权请求。'
});
});
module.exports = router;

View File

@ -48,23 +48,29 @@ app.use('/api', require('./routes/onboarding'));
// 执行状态查询路由(需认证)
app.use('/api/execution', require('./routes/execution'));
// 部署授权流程路由(需认证)
app.use('/api/approval', require('./routes/approval'));
// 根路由
app.get('/', function(_req, res) {
res.json({
status: 'ok',
service: 'guanghu-api-server',
version: '3.0.0',
version: '4.0.0',
channel: 'B',
description: '光湖后端中间层 · 语言驱动操作系统',
capabilities: ['read', 'write', 'intent-routing', 'permission-sandbox', 'onboarding', 'execution-guard']
capabilities: [
'read', 'write', 'intent-routing', 'permission-sandbox',
'onboarding', 'execution-guard', 'approval-flow', 'autonomy'
]
});
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, '127.0.0.1', function() {
console.log('🔗 光湖后端中间层启动 · 端口 ' + PORT);
console.log(' 通道B · 语言驱动操作系统');
console.log(' 能力:读取 + 写入 + 意图路由 + 权限沙箱 + 认知引导 + 执行保护');
console.log(' 通道B · 语言驱动操作系统 v4.0.0');
console.log(' 能力:读取 + 写入 + 意图路由 + 权限沙箱 + 认知引导 + 执行保护 + 授权流程 + 系统自治');
console.log(' 监听地址127.0.0.1:' + PORT + '(仅本机访问)');
// 启动执行看门狗

View File

@ -0,0 +1,123 @@
/**
* 系统自治运行引擎 · Phase 9
*
* 系统自主监控运行状态自动优化异常时主动报告
* 遵循自治规则不违反铁律不绕过天眼审核不绕过人类授权
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var fs = require('fs');
var path = require('path');
var autonomyRules = require('../config/autonomy-rules.json');
var AUTONOMY_LOG_DIR = process.env.AUTONOMY_LOG_DIR ||
path.join(__dirname, '../../logs/autonomy');
/**
* 写入自治日志所有自主操作记录到审计日志
*/
function writeAutonomyLog(entry) {
try {
fs.mkdirSync(AUTONOMY_LOG_DIR, { recursive: true });
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(AUTONOMY_LOG_DIR, 'autonomy-' + today + '.jsonl');
fs.appendFile(logFile, JSON.stringify(entry) + '\n', function(err) {
if (err) console.error('自治日志写入失败:', err.message);
});
} catch (e) {
console.error('自治日志写入失败:', e.message);
}
}
/**
* 检查自治操作是否合规
* @param {string} action - 操作描述
* @returns {Object} { allowed: boolean, reason: string }
*/
function checkAutonomyCompliance(action) {
var forbidden = autonomyRules.autonomyRules.systemMustNot;
var actionLower = action.toLowerCase();
// 检查是否触及禁止项
if (actionLower.includes('权限') && actionLower.includes('修改')) {
return { allowed: false, reason: '权限变更只有冰朔能触发' };
}
if (actionLower.includes('绕过') && actionLower.includes('天眼')) {
return { allowed: false, reason: '不得绕过天眼审核' };
}
if (actionLower.includes('绕过') && actionLower.includes('授权')) {
return { allowed: false, reason: '不得绕过人类授权步骤' };
}
return { allowed: true, reason: 'ok' };
}
/**
* 检测冰朔交互模式
* @param {string} message - 用户消息
* @returns {string} 'instruction' | 'natural'
*/
function detectInteractionMode(message) {
var instructionTriggers = autonomyRules.dualLineArchitecture.lineA.interactionModes.instructionMode.trigger;
var msgLower = message.toLowerCase();
for (var i = 0; i < instructionTriggers.length; i++) {
if (msgLower.includes(instructionTriggers[i].toLowerCase())) {
return 'instruction';
}
}
return 'natural';
}
/**
* 记录自主优化操作
* @param {string} action - 操作描述
* @param {Object} details - 操作详情
*/
function logAutonomousAction(action, details) {
var compliance = checkAutonomyCompliance(action);
if (!compliance.allowed) {
console.error('[AUTONOMY] 自治操作被阻止: ' + action + ' — ' + compliance.reason);
writeAutonomyLog({
type: 'blocked',
action: action,
reason: compliance.reason,
timestamp: new Date().toISOString()
});
return false;
}
writeAutonomyLog({
type: 'autonomous_action',
action: action,
details: details || {},
timestamp: new Date().toISOString()
});
return true;
}
/**
* 获取部署流水线阶段定义
*/
function getDeploymentStages() {
return autonomyRules.deploymentFlow.stages;
}
/**
* 获取双线架构配置
*/
function getDualLineConfig() {
return autonomyRules.dualLineArchitecture;
}
module.exports = {
checkAutonomyCompliance: checkAutonomyCompliance,
detectInteractionMode: detectInteractionMode,
logAutonomousAction: logAutonomousAction,
getDeploymentStages: getDeploymentStages,
getDualLineConfig: getDualLineConfig,
writeAutonomyLog: writeAutonomyLog
};

165
docs/css/approval.css Normal file
View File

@ -0,0 +1,165 @@
/**
* 部署授权样式 · Phase 8
* 版权国作登字-2026-A-00037559
*/
/* 授权提醒徽章 */
.approval-badge {
position: fixed;
top: 16px;
right: 16px;
background: #ff6b35;
color: #fff;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
cursor: pointer;
z-index: 9999;
box-shadow: 0 2px 12px rgba(255, 107, 53, 0.4);
animation: badge-pulse 2s infinite;
}
.approval-badge:hover {
background: #e65a2a;
}
@keyframes badge-pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* 授权容器 */
.approval-container {
position: fixed;
top: 60px;
right: 16px;
z-index: 9998;
display: none;
}
/* 授权面板 */
.approval-panel {
background: #1e1e2e;
border: 1px solid #333;
border-radius: 12px;
width: 380px;
max-height: 70vh;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.approval-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 16px;
border-bottom: 1px solid #333;
font-size: 15px;
font-weight: 600;
color: #e0e0e0;
}
.approval-close-btn {
background: none;
border: none;
color: #888;
font-size: 20px;
cursor: pointer;
padding: 0 4px;
}
.approval-close-btn:hover {
color: #fff;
}
.approval-panel-body {
padding: 8px;
}
/* 授权条目 */
.approval-item {
background: #252535;
border-radius: 8px;
padding: 12px;
margin-bottom: 8px;
}
.approval-item-info {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: center;
margin-bottom: 10px;
}
.approval-item-info strong {
color: #e0e0e0;
font-size: 14px;
}
.approval-channel {
background: #3a3a5a;
color: #aaa;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
}
.approval-time {
color: #666;
font-size: 12px;
}
/* 授权按钮 */
.approval-item-actions {
display: flex;
gap: 8px;
}
.approval-btn {
flex: 1;
padding: 8px 12px;
border: none;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: background 0.2s;
}
.approval-btn.approve {
background: rgba(15, 123, 108, 0.2);
color: #0F7B6C;
}
.approval-btn.approve:hover {
background: rgba(15, 123, 108, 0.4);
}
.approval-btn.reject {
background: rgba(212, 76, 71, 0.2);
color: #D44C47;
}
.approval-btn.reject:hover {
background: rgba(212, 76, 71, 0.4);
}
/* 通知消息 */
.approval-notification {
position: fixed;
bottom: 24px;
right: 24px;
background: #1e1e2e;
border: 1px solid #333;
color: #e0e0e0;
padding: 12px 20px;
border-radius: 8px;
font-size: 14px;
z-index: 10000;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
transition: opacity 0.5s;
}
.approval-notification.fade-out {
opacity: 0;
}

230
docs/js/approval-ui.js Normal file
View File

@ -0,0 +1,230 @@
/**
* 部署授权 UI · Phase 8
*
* 授权人在交互页面收到授权请求推送时展示授权弹窗
* 授权人必须主动点击确认不回应视为不通过
* 系统绝不自动跳过授权步骤
*
* 版权国作登字-2026-A-00037559
*/
/* global HOLOLAKE_ENV */
(function(window) {
'use strict';
var API_BASE = (typeof HOLOLAKE_ENV !== 'undefined' && HOLOLAKE_ENV === 'production')
? 'https://guanghulab.com/api' : '';
/**
* ApprovalUI 构造函数
*/
function ApprovalUI() {
this._pollTimer = null;
this._container = null;
}
/**
* 初始化开始轮询待授权请求
* @param {string} devId - 当前登录的开发者编号
* @param {string} token - 认证 token
*/
ApprovalUI.prototype.init = function(devId, token) {
this.devId = devId;
this.token = token;
this._ensureContainer();
this._startPolling();
};
/**
* 确保授权容器存在
*/
ApprovalUI.prototype._ensureContainer = function() {
if (this._container) return;
this._container = document.createElement('div');
this._container.id = 'approval-container';
this._container.className = 'approval-container';
document.body.appendChild(this._container);
};
/**
* 开始轮询待授权请求 30
*/
ApprovalUI.prototype._startPolling = function() {
var self = this;
if (!API_BASE) return;
var poll = function() {
self._fetchPendingApprovals();
};
poll();
this._pollTimer = setInterval(poll, 30000);
};
/**
* 停止轮询
*/
ApprovalUI.prototype.destroy = function() {
if (this._pollTimer) {
clearInterval(this._pollTimer);
this._pollTimer = null;
}
};
/**
* 获取待处理的授权请求
*/
ApprovalUI.prototype._fetchPendingApprovals = function() {
var self = this;
if (!API_BASE) return;
fetch(API_BASE + '/approval', {
headers: {
'x-dev-id': this.devId,
'Authorization': 'Bearer ' + this.token
}
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.success && data.pending && data.pending.length > 0) {
self._showApprovalBadge(data.pending.length);
self._renderPendingList(data.pending);
} else {
self._hideApprovalBadge();
}
})
.catch(function() { /* 静默失败 */ });
};
/**
* 显示授权提醒徽章
*/
ApprovalUI.prototype._showApprovalBadge = function(count) {
var badge = document.getElementById('approval-badge');
if (!badge) {
badge = document.createElement('div');
badge.id = 'approval-badge';
badge.className = 'approval-badge';
document.body.appendChild(badge);
}
badge.textContent = '🔔 ' + count + ' 个待授权';
badge.style.display = 'block';
var self = this;
badge.onclick = function() { self._togglePanel(); };
};
/**
* 隐藏授权徽章
*/
ApprovalUI.prototype._hideApprovalBadge = function() {
var badge = document.getElementById('approval-badge');
if (badge) badge.style.display = 'none';
if (this._container) this._container.style.display = 'none';
};
/**
* 切换授权面板显示
*/
ApprovalUI.prototype._togglePanel = function() {
if (this._container) {
this._container.style.display =
this._container.style.display === 'none' ? 'block' : 'none';
}
};
/**
* 渲染待授权列表
*/
ApprovalUI.prototype._renderPendingList = function(pending) {
if (!this._container) return;
var html = '<div class="approval-panel">' +
'<div class="approval-panel-header">' +
'<span>📋 待授权请求</span>' +
'<button class="approval-close-btn" id="approval-close">×</button>' +
'</div>' +
'<div class="approval-panel-body">';
for (var i = 0; i < pending.length; i++) {
var item = pending[i];
html += '<div class="approval-item" data-id="' + item.id + '">' +
'<div class="approval-item-info">' +
'<strong>' + item.module + '</strong>' +
'<span class="approval-channel">' + item.channel + '</span>' +
'<span class="approval-time">' + new Date(item.createdAt).toLocaleString('zh-CN') + '</span>' +
'</div>' +
'<div class="approval-item-actions">' +
'<button class="approval-btn approve" data-id="' + item.id + '" data-action="approved">✅ 授权通过</button>' +
'<button class="approval-btn reject" data-id="' + item.id + '" data-action="rejected">❌ 拒绝</button>' +
'</div></div>';
}
html += '</div></div>';
this._container.innerHTML = html;
this._container.style.display = 'block';
// 绑定事件
var self = this;
var closeBtn = document.getElementById('approval-close');
if (closeBtn) {
closeBtn.onclick = function() { self._container.style.display = 'none'; };
}
var buttons = this._container.querySelectorAll('.approval-btn');
for (var j = 0; j < buttons.length; j++) {
buttons[j].addEventListener('click', function() {
var approvalId = this.getAttribute('data-id');
var action = this.getAttribute('data-action');
self._submitDecision(approvalId, action);
});
}
};
/**
* 提交授权决定
*/
ApprovalUI.prototype._submitDecision = function(approvalId, decision) {
var self = this;
if (!API_BASE) return;
fetch(API_BASE + '/approval/' + approvalId + '/decide', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-dev-id': this.devId,
'Authorization': 'Bearer ' + this.token
},
body: JSON.stringify({ decision: decision })
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.reply) {
self._showNotification(data.reply);
}
// 刷新列表
self._fetchPendingApprovals();
})
.catch(function(err) {
self._showNotification('❌ 操作失败:' + err.message);
});
};
/**
* 显示通知消息
*/
ApprovalUI.prototype._showNotification = function(message) {
var notif = document.createElement('div');
notif.className = 'approval-notification';
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(function() {
notif.classList.add('fade-out');
setTimeout(function() { notif.remove(); }, 500);
}, 4000);
};
// 导出到全局
window.ApprovalUI = ApprovalUI;
})(window);