Files
git-website-engine-control-…/src/components/ConfigAndAuditTab.tsx
T

333 lines
13 KiB
TypeScript

import React, { useState } from "react";
import {
ShieldCheck,
Key,
FileCode,
CheckCircle2,
Plus,
Clock,
GitCommit,
Save,
Lock,
Code,
} from "lucide-react";
import { CredentialRef, AuditLogEntry, SiteConfig } from "../types";
interface ConfigAndAuditTabProps {
credentials: CredentialRef[];
auditLogs: AuditLogEntry[];
siteConfig: SiteConfig;
onAddCredential: (
name: string,
type: CredentialRef["type"],
val: string,
) => Promise<void>;
onSaveSiteConfig: (rawYaml: string) => Promise<void>;
}
export const ConfigAndAuditTab: React.FC<ConfigAndAuditTabProps> = ({
credentials,
auditLogs,
siteConfig,
onAddCredential,
onSaveSiteConfig,
}) => {
const [activeSubTab, setActiveSubTab] = useState<
"credentials" | "audit" | "yaml"
>("yaml");
const [yamlContent, setYamlContent] = useState(siteConfig.rawYaml);
const [yamlSavedMsg, setYamlSavedMsg] = useState<string | null>(null);
// New Credential Form
const [showAddCredModal, setShowAddCredModal] = useState(false);
const [credName, setCredName] = useState("");
const [credType, setCredType] = useState<CredentialRef["type"]>("ssh_key");
const [credVal, setCredVal] = useState("");
const handleSaveYaml = async () => {
try {
await onSaveSiteConfig(yamlContent);
setYamlSavedMsg(
"Updated site.yaml successfully! Created Git commit on site-definition repository.",
);
setTimeout(() => setYamlSavedMsg(null), 4000);
} catch (err: any) {
alert(`YAML Save Error: ${err.message}`);
}
};
const handleCreateCred = async (e: React.FormEvent) => {
e.preventDefault();
if (!credName || !credVal) return;
await onAddCredential(credName, credType, credVal);
setShowAddCredModal(false);
setCredName("");
setCredVal("");
};
return (
<div className="space-y-6">
{/* HEADER */}
<div className="bg-[#111111] rounded-sm border border-[#1F1F1F] p-6 shadow-xs flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<div className="flex items-center space-x-2">
<h1 className="text-xl font-serif italic text-white">
System Settings & Audit
</h1>
<span className="px-2.5 py-0.5 rounded-xs text-[10px] uppercase font-bold tracking-widest bg-emerald-950/40 text-emerald-400 border border-emerald-500/20">
Control Plane Configuration
</span>
</div>
<p className="text-xs text-neutral-400 mt-1">
Manage global site.yaml settings, secret credential references, and
view system audit logs.
</p>
</div>
</div>
{/* SUB-TAB NAV */}
<div className="flex border-b border-[#1F1F1F] space-x-6 text-xs font-semibold uppercase tracking-wider">
<button
onClick={() => setActiveSubTab("yaml")}
className={`pb-3 transition-colors flex items-center space-x-2 border-b-2 ${
activeSubTab === "yaml"
? "border-[#C5A059] text-[#C5A059]"
: "border-transparent text-neutral-500 hover:text-neutral-300"
}`}
>
<FileCode className="w-4 h-4" />
<span>Declarative site.yaml (Git Commits)</span>
</button>
<button
onClick={() => setActiveSubTab("credentials")}
className={`pb-3 transition-colors flex items-center space-x-2 border-b-2 ${
activeSubTab === "credentials"
? "border-[#C5A059] text-[#C5A059]"
: "border-transparent text-neutral-500 hover:text-neutral-300"
}`}
>
<Key className="w-4 h-4" />
<span>Secret Credential References ({credentials.length})</span>
</button>
<button
onClick={() => setActiveSubTab("audit")}
className={`pb-3 transition-colors flex items-center space-x-2 border-b-2 ${
activeSubTab === "audit"
? "border-[#C5A059] text-[#C5A059]"
: "border-transparent text-neutral-500 hover:text-neutral-300"
}`}
>
<Clock className="w-4 h-4" />
<span>Audit Trail Log ({auditLogs.length})</span>
</button>
</div>
{activeSubTab === "yaml" ? (
<div className="space-y-4">
{yamlSavedMsg && (
<div className="p-4 bg-emerald-950/40 border border-emerald-500/20 text-emerald-300 text-xs rounded-sm flex items-center space-x-2">
<CheckCircle2 className="w-4 h-4 text-emerald-400" />
<span>{yamlSavedMsg}</span>
</div>
)}
<div className="bg-[#111111] rounded-sm p-6 border border-[#1F1F1F] space-y-4">
<div className="flex items-center justify-between text-white border-b border-[#1F1F1F] pb-3">
<div className="flex items-center space-x-2">
<Code className="w-5 h-5 text-[#C5A059]" />
<span className="font-mono font-bold text-sm">/site.yaml</span>
<span className="text-xs text-neutral-400">
Canonical Protocol Model
</span>
</div>
<button
onClick={handleSaveYaml}
className="px-4 py-2 bg-[#C5A059] hover:bg-[#b38f4a] text-black rounded-sm text-xs font-semibold shadow-sm transition-all flex items-center space-x-1.5 uppercase tracking-wider"
>
<GitCommit className="w-3.5 h-3.5" />
<span>Save & Commit to Git</span>
</button>
</div>
<textarea
rows={16}
value={yamlContent}
onChange={(e) => setYamlContent(e.target.value)}
className="w-full bg-[#0A0A0A] text-[#C5A059] font-mono text-xs p-4 rounded-sm border border-[#1F1F1F] focus:outline-none focus:border-[#C5A059]"
/>
</div>
</div>
) : activeSubTab === "credentials" ? (
<div className="space-y-4">
<div className="flex justify-between items-center">
<span className="text-[10px] font-bold text-neutral-500 uppercase tracking-widest">
Secret Reference Vault
</span>
<button
onClick={() => setShowAddCredModal(true)}
className="px-3.5 py-1.5 bg-[#C5A059] hover:bg-[#b38f4a] text-black rounded-sm text-xs font-semibold flex items-center space-x-1 uppercase tracking-wider"
>
<Plus className="w-3.5 h-3.5" />
<span>Store Secret Reference</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-xs font-mono">
{credentials.map((c) => (
<div
key={c.id}
className="p-4 bg-[#111111] rounded-sm border border-[#1F1F1F] shadow-xs space-y-2"
>
<div className="flex items-center justify-between">
<span className="font-bold text-white text-sm truncate">
{c.name}
</span>
<Key className="w-4 h-4 text-[#C5A059] shrink-0" />
</div>
<div className="text-[11px] text-neutral-400 space-y-1">
<div>
Reference ID:{" "}
<code className="text-[#C5A059] font-bold">{c.id}</code>
</div>
<div>
Type:{" "}
<span className="uppercase text-neutral-500">{c.type}</span>
</div>
<div className="p-2 bg-[#0A0A0A] rounded-sm border border-[#1F1F1F] text-neutral-300 truncate font-mono text-[10px]">
{c.maskedValue}
</div>
<div className="pt-2 text-neutral-500 text-[10px] flex items-center justify-between">
<span>Used {c.usageCount} times</span>
<span>
Created: {new Date(c.createdAt).toLocaleDateString()}
</span>
</div>
</div>
</div>
))}
</div>
</div>
) : (
/* AUDIT LOG TRAIL */
<div className="bg-[#111111] rounded-sm border border-[#1F1F1F] p-6 shadow-xs space-y-4">
<h2 className="text-base font-bold text-white">
System Audit Trail Log
</h2>
<div className="bg-[#0A0A0A] text-neutral-200 font-mono text-xs rounded-sm p-4 border border-[#1F1F1F] space-y-2 max-h-[500px] overflow-y-auto">
{auditLogs.map((log) => (
<div
key={log.id}
className="py-2 border-b border-[#141414] last:border-0 flex items-start justify-between"
>
<div>
<div className="flex items-center space-x-2">
<span className="text-[#C5A059] font-bold">
[{log.category.toUpperCase()}]
</span>
<span className="text-white font-semibold">
{log.action}
</span>
<span className="text-[10px] text-emerald-400 bg-emerald-950/40 border border-emerald-500/20 px-1.5 py-0.5 rounded-xs font-mono uppercase font-bold">
{log.result}
</span>
</div>
<p className="text-neutral-400 text-[11px] mt-1">
{log.details}
</p>
</div>
<span className="text-neutral-500 text-[10px]">
{new Date(log.timestamp).toLocaleTimeString()}
</span>
</div>
))}
</div>
</div>
)}
{/* ADD CREDENTIAL MODAL */}
{showAddCredModal && (
<div className="fixed inset-0 bg-black/80 backdrop-blur-xs z-50 flex items-center justify-center p-4">
<div className="bg-[#111111] rounded-sm border border-[#1F1F1F] max-w-md w-full p-6 shadow-2xl space-y-4">
<div className="flex items-center justify-between border-b border-[#1F1F1F] pb-3">
<h3 className="text-base font-bold text-white">
Store New Secret Reference
</h3>
<button
onClick={() => setShowAddCredModal(false)}
className="text-neutral-500 hover:text-white text-xl leading-none"
>
&times;
</button>
</div>
<form onSubmit={handleCreateCred} className="space-y-4 text-xs">
<div>
<label className="block font-semibold text-neutral-300 mb-1">
Secret Name
</label>
<input
type="text"
required
placeholder="e.g. Gitea Internal Deploy Key"
value={credName}
onChange={(e) => setCredName(e.target.value)}
className="w-full px-3 py-2 bg-[#0A0A0A] border border-[#1F1F1F] rounded-sm text-white placeholder-neutral-600 focus:outline-none focus:border-[#C5A059]"
/>
</div>
<div>
<label className="block font-semibold text-neutral-300 mb-1">
Type
</label>
<select
value={credType}
onChange={(e) => setCredType(e.target.value as any)}
className="w-full px-3 py-2 bg-[#0A0A0A] border border-[#1F1F1F] rounded-sm text-white font-mono focus:outline-none focus:border-[#C5A059]"
>
<option value="ssh_key">SSH Private Key</option>
<option value="token">OAuth / Deploy Token</option>
<option value="password">Password</option>
</select>
</div>
<div>
<label className="block font-semibold text-neutral-300 mb-1">
Secret Value (Encrypted at rest, never written to Git)
</label>
<textarea
rows={4}
required
placeholder="-----BEGIN OPENSSH PRIVATE KEY----- ..."
value={credVal}
onChange={(e) => setCredVal(e.target.value)}
className="w-full px-3 py-2 bg-[#0A0A0A] border border-[#1F1F1F] rounded-sm text-white font-mono focus:outline-none focus:border-[#C5A059]"
/>
</div>
<div className="flex items-center justify-end space-x-3 pt-3 border-t border-[#1F1F1F]">
<button
type="button"
onClick={() => setShowAddCredModal(false)}
className="px-4 py-2 border border-[#222222] text-neutral-300 rounded-sm hover:bg-[#1A1A1A]"
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 bg-[#C5A059] text-black font-semibold rounded-sm hover:bg-[#b38f4a] uppercase tracking-wider"
>
Save Secret Reference
</button>
</div>
</form>
</div>
</div>
)}
</div>
);
};