feat(hands): restructure Hands UI with Chinese localization

Major changes:
- Add HandList.tsx component for left sidebar
- Add HandTaskPanel.tsx for middle content area
- Restructure Sidebar tabs: 分身/HANDS/Workflow
- Remove Hands tab from RightPanel
- Localize all UI text to Chinese
- Archive legacy OpenClaw documentation
- Add Hands integration lessons document
- Update feature checklist with new components

UI improvements:
- Left sidebar now shows Hands list with status icons
- Middle area shows selected Hand's tasks and results
- Consistent styling with Tailwind CSS
- Chinese status labels and buttons

Documentation:
- Create docs/archive/openclaw-legacy/ for old docs
- Add docs/knowledge-base/hands-integration-lessons.md
- Update docs/knowledge-base/feature-checklist.md
- Update docs/knowledge-base/README.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-03-14 23:16:32 +08:00
parent 67e1da635d
commit 07079293f4
126 changed files with 36229 additions and 1035 deletions

View File

@@ -0,0 +1,108 @@
/**
* AuditLogsPanel - OpenFang Audit Logs UI
*
* Displays OpenFang's Merkle hash chain audit logs.
*/
import { useState, useEffect } from 'react';
import { useGatewayStore } from '../store/gatewayStore';
export function AuditLogsPanel() {
const { auditLogs, loadAuditLogs, isLoading } = useGatewayStore();
const [limit, setLimit] = useState(50);
useEffect(() => {
loadAuditLogs({ limit });
}, [loadAuditLogs, limit]);
const formatTimestamp = (timestamp: string) => {
try {
return new Date(timestamp).toLocaleString('zh-CN');
} catch {
return timestamp;
}
};
const resultColor = {
success: 'text-green-600 dark:text-green-400',
failure: 'text-red-600 dark:text-red-400',
};
if (isLoading && auditLogs.length === 0) {
return (
<div className="p-4 text-center text-gray-500 dark:text-gray-400">
...
</div>
);
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">
</h2>
<div className="flex items-center gap-2">
<select
value={limit}
onChange={(e) => setLimit(Number(e.target.value))}
className="text-sm border border-gray-200 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-700 dark:text-gray-300 px-2 py-1"
>
<option value={25}>25 </option>
<option value={50}>50 </option>
<option value={100}>100 </option>
<option value={200}>200 </option>
</select>
<button
onClick={() => loadAuditLogs({ limit })}
className="text-sm text-blue-600 dark:text-blue-400 hover:underline"
>
</button>
</div>
</div>
{auditLogs.length === 0 ? (
<div className="p-4 text-center text-gray-500 dark:text-gray-400">
</div>
) : (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b border-gray-200 dark:border-gray-700">
<th className="text-left py-2 px-3 font-medium text-gray-700 dark:text-gray-300"></th>
<th className="text-left py-2 px-3 font-medium text-gray-700 dark:text-gray-300"></th>
<th className="text-left py-2 px-3 font-medium text-gray-700 dark:text-gray-300"></th>
<th className="text-left py-2 px-3 font-medium text-gray-700 dark:text-gray-300"></th>
</tr>
</thead>
<tbody>
{auditLogs.map((log, index) => (
<tr
key={log.id || index}
className="border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50"
>
<td className="py-2 px-3 text-gray-600 dark:text-gray-400">
{formatTimestamp(log.timestamp)}
</td>
<td className="py-2 px-3 text-gray-900 dark:text-white">
{log.action}
</td>
<td className="py-2 px-3 text-gray-600 dark:text-gray-400">
{log.actor || '-'}
</td>
<td className={`py-2 px-3 ${log.result ? resultColor[log.result] : 'text-gray-600 dark:text-gray-400'}`}>
{log.result === 'success' ? '成功' : log.result === 'failure' ? '失败' : '-'}
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
);
}
export default AuditLogsPanel;