Files
kuma-strapper/frontend/src/components/ApprovalModal.jsx
Debian ea49143a13
All checks were successful
Build Container / build (push) Successful in 1m18s
Initial commit with CI workflow
- Flask backend with SSH discovery and Claude AI integration
- React/Vite frontend with Tailwind CSS
- Docker multi-stage build
- Gitea Actions workflow for container builds

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 21:38:50 +00:00

108 lines
4.4 KiB
JavaScript

import { useState } from 'react';
export default function ApprovalModal({ approvals, onApprove, onReject }) {
const [processing, setProcessing] = useState({});
const handleApprove = async (id) => {
setProcessing(p => ({ ...p, [id]: 'approving' }));
try {
await onApprove(id);
} finally {
setProcessing(p => ({ ...p, [id]: null }));
}
};
const handleReject = async (id) => {
setProcessing(p => ({ ...p, [id]: 'rejecting' }));
try {
await onReject(id);
} finally {
setProcessing(p => ({ ...p, [id]: null }));
}
};
if (approvals.length === 0) return null;
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-slate-800 rounded-lg shadow-xl max-w-2xl w-full max-h-[80vh] overflow-hidden">
<div className="bg-amber-600 px-6 py-4">
<h2 className="text-xl font-bold text-white flex items-center gap-2">
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
Pending Approvals ({approvals.length})
</h2>
</div>
<div className="overflow-y-auto max-h-[60vh]">
{approvals.map((approval) => (
<div key={approval.id} className="border-b border-slate-700 p-6">
<div className="flex items-start justify-between mb-4">
<div>
<span className={`inline-block px-2 py-1 text-xs font-medium rounded ${
approval.type === 'ssh_command'
? 'bg-blue-900 text-blue-200'
: 'bg-purple-900 text-purple-200'
}`}>
{approval.type === 'ssh_command' ? 'SSH Command' : 'Create Monitor'}
</span>
<h3 className="text-lg font-semibold text-slate-100 mt-2">
{approval.description}
</h3>
</div>
</div>
{approval.reason && (
<div className="mb-4 p-3 bg-slate-700/50 rounded">
<p className="text-sm text-slate-300">
<span className="font-medium text-slate-200">Why: </span>
{approval.reason}
</p>
</div>
)}
{approval.type === 'ssh_command' && (
<div className="mb-4">
<p className="text-xs text-slate-400 mb-1">Command to execute:</p>
<code className="block p-3 bg-slate-900 rounded text-sm text-green-400 font-mono overflow-x-auto">
{approval.details.command}
</code>
<p className="text-xs text-slate-500 mt-1">
On host: {approval.details.hostname}
</p>
</div>
)}
{approval.type === 'create_monitor' && (
<div className="mb-4 p-3 bg-slate-700/50 rounded text-sm">
<p><span className="text-slate-400">Name:</span> {approval.details.name}</p>
<p><span className="text-slate-400">Type:</span> {approval.details.type}</p>
<p><span className="text-slate-400">Target:</span> {approval.details.target}</p>
</div>
)}
<div className="flex gap-3">
<button
onClick={() => handleApprove(approval.id)}
disabled={processing[approval.id]}
className="flex-1 px-4 py-2 bg-green-600 hover:bg-green-500 disabled:bg-green-800 text-white font-medium rounded transition-colors"
>
{processing[approval.id] === 'approving' ? 'Approving...' : 'Approve'}
</button>
<button
onClick={() => handleReject(approval.id)}
disabled={processing[approval.id]}
className="flex-1 px-4 py-2 bg-red-600 hover:bg-red-500 disabled:bg-red-800 text-white font-medium rounded transition-colors"
>
{processing[approval.id] === 'rejecting' ? 'Rejecting...' : 'Reject'}
</button>
</div>
</div>
))}
</div>
</div>
</div>
);
}