import { useState, useMemo } from "react";
const STATUS_OPTIONS = [
"Completed Upgrade",
"Completed Traffic Swing",
"Decommed",
"For Decom (Housekeeping)",
"For Decom (Upgrade Not Needed)",
"For Decomm",
"Access",
"In Progress",
"Pending",
"",
];
const STATUS_COLORS = {
"Completed Upgrade": { bg: "#1a3a5c", text: "#7ec8f4", dot: "#4da6d8" },
"Completed Traffic Swing": { bg: "#0e2a40", text: "#5bbcf0", dot: "#3399cc" },
"Decommed": { bg: "#1a3d1a", text: "#7dcc7d", dot: "#4caf50" },
"For Decom (Housekeeping)": { bg: "#2a3d14", text: "#b5e07a", dot: "#8bc34a" },
"For Decom (Upgrade Not Needed)": { bg: "#3d2e00", text: "#f5c842", dot: "#e6a817" },
"For Decomm": { bg: "#3d2e00", text: "#f5c842", dot: "#e6a817" },
"Access": { bg: "#3d1a2e", text: "#f07ab5", dot: "#e91e8c" },
"In Progress": { bg: "#1a2a3d", text: "#80b3ff", dot: "#4488ff" },
"Pending": { bg: "#2d2d2d", text: "#aaaaaa", dot: "#888888" },
"": { bg: "#1e1e1e", text: "#666", dot: "#555" },
};
const ENV_COLORS = {
Prod: { bg: "#2a1a1a", text: "#ff7c7c", border: "#c0392b" },
NonProd: { bg: "#1a2a1a", text: "#7ccc7c", border: "#27ae60" },
};
const RAW_DATA = [
{ name: "amex-db-cluster4pro", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "amex-db-cluster4pro-replica", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "bifrost-prod", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "buyload-test", tribe: "B2C", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "cluster5-shared-db-uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "ctr4pushnotifsvcdbpro", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "ctr4pushnotifsvcdbpro-replica", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "db-optimization-prod", tribe: "DBOps", environment: "NonProd", status: "", notes: "" },
{ name: "devtools-perf", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "gloanrds-prd", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "gloanrds-prd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "golbat", tribe: "B2B", environment: "Prod", status: "Decommed", notes: "" },
{ name: "golbat-replica", tribe: "B2B", environment: "Prod", status: "Decommed", notes: "" },
{ name: "knowhere", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "konga", tribe: "Technology", environment: "Prod", status: "Access", notes: "" },
{ name: "konga-kong-cluster-production", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "konga-kong-cluster-uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "l2-ackerman", tribe: "L2", environment: "Prod", status: "", notes: "" },
{ name: "marvel", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "octdbprod", tribe: "Enterprise Services", environment: "Prod", status: "", notes: "" },
{ name: "octdbprod-old1", tribe: "Enterprise Services", environment: "Prod", status: "Decommed", notes: "" },
{ name: "partner-notifs-prd", tribe: "Lending", environment: "Prod", status: "Access", notes: "binlog_format (Mixed)" },
{ name: "partner-notifs-prd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "payments-pickaxe-production", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "payments-pickaxe-replica-production", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "promocode", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "promocode-replica", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "rcbc", tribe: "Funds", environment: "Prod", status: "", notes: "No objects" },
{ name: "rcbc-replica", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "sem-automation-rds", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "sendmoney-kkb-replica", tribe: "Funds", environment: "Prod", status: "For Decomm", notes: "" },
{ name: "sendmoney-perf", tribe: "Funds", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "shared-db-cluster1uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster1uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster2uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster2uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster4uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster4uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-rds-sit", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-rds-sit-16-june-test", tribe: "Technology", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "sqlag-rds-uat", tribe: "DBOps", environment: "NonProd", status: "", notes: "MTCSD-287542" },
{ name: "telcoscore-db-prod", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "telcoscore-db-prod-replica", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "telcoscore-db-uat", tribe: "New Business", environment: "NonProd", status: "", notes: "" },
{ name: "unicorn-rds-sit", tribe: "New Business", environment: "NonProd", status: "", notes: "" },
{ name: "user-consent-serv", tribe: "Customer and Integration", environment: "Prod", status: "", notes: "" },
{ name: "user-consent-serv-replica", tribe: "Customer and Integration", environment: "Prod", status: "", notes: "" },
{ name: "ussd", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "ussd-replica", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "victoria-prd", tribe: "Lending", environment: "Prod", status: "Access", notes: "" },
{ name: "victoria-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "vmacqdpd01", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "vmacqdpd01-replica", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "vmbaiddv01", tribe: "Funds", environment: "NonProd", status: "", notes: "" },
{ name: "vmcladpd01", tribe: "Segments", environment: "Prod", status: "", notes: "" },
{ name: "vmcladpd02", tribe: "Segments", environment: "Prod", status: "", notes: "" },
{ name: "vmcladut01", tribe: "Segments", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
{ name: "vmcladut02", tribe: "Segments", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
{ name: "vmpokappdpd03", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokappdpd03-replica", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdisdpd03", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdisdpd03-replica", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdispduat01", tribe: "B2B", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "vmpokdispduat01-old1", tribe: "B2B", environment: "NonProd", status: "Decommed", notes: "Done April 23" },
{ name: "vmpokpduat01", tribe: "B2B", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "vmrqrddv01", tribe: "Segments", environment: "NonProd", status: "", notes: "" },
{ name: "vmrqrdpd01", tribe: "Segments", environment: "Prod", status: "Access", notes: "" },
{ name: "wiirdsmysql", tribe: "Lending", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "wiirdsmysql-replica", tribe: "Lending", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "wiirdsprd", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "wiirdsprd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "wire-sms-production", tribe: "Technology", environment: "Prod", status: "", notes: "binlog_format (Mixed)" },
{ name: "wire-sms-production-replica", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "xrecon-logs-uat", tribe: "Enterprise Services", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
];
const TRIBES = [...new Set(RAW_DATA.map(r => r.tribe))].sort();
function StatusBadge({ status }) {
const c = STATUS_COLORS[status] || STATUS_COLORS[""];
return (
<span style={{
background: c.bg,
color: c.text,
border: 1px solid ${c.dot}33,
borderRadius: 4,
padding: "2px 8px",
fontSize: 11,
fontFamily: "'IBM Plex Mono', monospace",
whiteSpace: "nowrap",
display: "inline-flex",
alignItems: "center",
gap: 5,
}}>
<span style={{ width: 6, height: 6, borderRadius: "50%", background: c.dot, flexShrink: 0 }} />
{status || "—"}
);
}
function EnvBadge({ env }) {
const c = ENV_COLORS[env] || { bg: "#222", text: "#aaa", border: "#555" };
return (
<span style={{
background: c.bg,
color: c.text,
border: 1px solid ${c.border}55,
borderRadius: 3,
padding: "1px 7px",
fontSize: 11,
fontFamily: "'IBM Plex Mono', monospace",
letterSpacing: "0.03em",
}}>{env}
);
}
export default function App() {
const [data, setData] = useState(RAW_DATA.map((r, i) => ({ ...r, id: i })));
const [editingId, setEditingId] = useState(null);
const [editValues, setEditValues] = useState({});
const [filterTribe, setFilterTribe] = useState("All");
const [filterEnv, setFilterEnv] = useState("All");
const [filterStatus, setFilterStatus] = useState("All");
const [search, setSearch] = useState("");
const [showAddRow, setShowAddRow] = useState(false);
const [newRow, setNewRow] = useState({ name: "", tribe: "B2C", environment: "Prod", status: "", notes: "" });
const [activeTab, setActiveTab] = useState("table");
const filtered = useMemo(() => data.filter(r => {
if (filterTribe !== "All" && r.tribe !== filterTribe) return false;
if (filterEnv !== "All" && r.environment !== filterEnv) return false;
if (filterStatus !== "All" && r.status !== filterStatus) return false;
if (search && !r.name.toLowerCase().includes(search.toLowerCase()) && !r.tribe.toLowerCase().includes(search.toLowerCase())) return false;
return true;
}), [data, filterTribe, filterEnv, filterStatus, search]);
const summary = useMemo(() => {
const counts = {};
data.forEach(r => {
const k = r.status || "Pending";
counts[k] = (counts[k] || 0) + 1;
});
return counts;
}, [data]);
const tribeBreakdown = useMemo(() => {
const counts = {};
data.forEach(r => {
counts[r.tribe] = (counts[r.tribe] || 0) + 1;
});
return Object.entries(counts).sort((a, b) => b[1] - a[1]);
}, [data]);
function startEdit(row) {
setEditingId(row.id);
setEditValues({ status: row.status, notes: row.notes, tribe: row.tribe, environment: row.environment });
}
function saveEdit(id) {
setData(prev => prev.map(r => r.id === id ? { ...r, ...editValues } : r));
setEditingId(null);
}
function deleteRow(id) {
setData(prev => prev.filter(r => r.id !== id));
}
function addRow() {
if (!newRow.name.trim()) return;
setData(prev => [...prev, { ...newRow, id: Date.now() }]);
setNewRow({ name: "", tribe: "B2C", environment: "Prod", status: "", notes: "" });
setShowAddRow(false);
}
const allStatuses = [...new Set(data.map(r => r.status))].sort();
return (
<div style={{
minHeight: "100vh",
background: "#0d0d0d",
color: "#e0e0e0",
fontFamily: "'IBM Plex Sans', 'Segoe UI', sans-serif",
padding: "0",
}}>
{/* Header */}
<div style={{
background: "linear-gradient(135deg, #0a1628 0%, #0d1f3c 50%, #091a2e 100%)",
borderBottom: "1px solid #1e3a5f",
padding: "20px 28px 0",
}}>
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
<span style={{
background: "#1a4a8a",
color: "#7ec8f4",
fontSize: 10,
padding: "2px 8px",
borderRadius: 2,
letterSpacing: "0.12em",
fontFamily: "'IBM Plex Mono', monospace",
fontWeight: 600,
}}>MYSQL 8.4
<span style={{ color: "#3a6a9a", fontSize: 11 }}>RDS UPGRADE TRACKER
<h1 style={{
margin: 0,
fontSize: 22,
fontWeight: 700,
color: "#d4e9ff",
letterSpacing: "-0.01em",
}}>MySQL RDS Upgrade Dashboard
<p style={{ margin: "4px 0 0", fontSize: 12, color: "#4a7aaa" }}>
Total: <strong style={{ color: "#7ec8f4" }}>{data.length} instances tracked
<button
onClick={() => setShowAddRow(!showAddRow)}
style={{
background: "#1a4a8a",
color: "#7ec8f4",
border: "1px solid #2a6ab5",
borderRadius: 5,
padding: "7px 14px",
fontSize: 12,
cursor: "pointer",
fontFamily: "inherit",
display: "flex",
alignItems: "center",
gap: 6,
}}>
+ Add Instance
{/* Tabs */}
<div style={{ display: "flex", gap: 0 }}>
{["table", "summary"].map(tab => (
<button key={tab} onClick={() => setActiveTab(tab)} style={{
background: activeTab === tab ? "#0d0d0d" : "transparent",
color: activeTab === tab ? "#7ec8f4" : "#4a7aaa",
border: "none",
borderTop: activeTab === tab ? "2px solid #4a9adf" : "2px solid transparent",
borderLeft: "1px solid " + (activeTab === tab ? "#1e3a5f" : "transparent"),
borderRight: "1px solid " + (activeTab === tab ? "#1e3a5f" : "transparent"),
padding: "8px 18px",
fontSize: 12,
fontFamily: "inherit",
cursor: "pointer",
letterSpacing: "0.06em",
textTransform: "uppercase",
}}>
{tab === "table" ? "Instance Table" : "Summary View"}
</button>
))}
</div>
</div>
<div style={{ padding: "20px 28px" }}>
{/* Add Row Form */}
{showAddRow && (
<div style={{
background: "#111c2e",
border: "1px solid #1e3a5f",
borderRadius: 8,
padding: "16px",
marginBottom: 20,
display: "grid",
gridTemplateColumns: "2fr 1fr 1fr 1.5fr 2fr auto",
gap: 10,
alignItems: "end",
}}>
{[
["RDS Name", "name", "text"],
["Tribe", "tribe", "tribe-select"],
["Environment", "environment", "env-select"],
["Status", "status", "status-select"],
["Notes", "notes", "text"],
].map(([label, key, type]) => (
<div key={key}>
<div style={{ fontSize: 10, color: "#4a7aaa", marginBottom: 4, letterSpacing: "0.08em" }}>{label}</div>
{type === "text" ? (
<input value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "'IBM Plex Mono', monospace", boxSizing: "border-box" }} />
) : type === "tribe-select" ? (
<select value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "inherit" }}>
{TRIBES.map(t => <option key={t}>{t}</option>)}
</select>
) : type === "env-select" ? (
<select value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "inherit" }}>
<option>Prod</option><option>NonProd</option>
</select>
) : (
<select value={newRow[key]} onChange={e => setNewRow(p => ({ ...p, [key]: e.target.value }))}
style={{ width: "100%", background: "#0d1f3c", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 8px", fontSize: 12, fontFamily: "inherit" }}>
{STATUS_OPTIONS.map(s => <option key={s} value={s}>{s || "— none —"}</option>)}
</select>
)}
</div>
))}
<button onClick={addRow} style={{
background: "#1a4a8a", color: "#7ec8f4", border: "1px solid #2a6ab5",
borderRadius: 4, padding: "7px 14px", fontSize: 12, cursor: "pointer", fontFamily: "inherit"
}}>Add</button>
</div>
)}
{activeTab === "table" && (
<>
{/* Filters */}
<div style={{ display: "flex", gap: 10, marginBottom: 16, flexWrap: "wrap", alignItems: "center" }}>
<input placeholder="🔍 Search name or tribe…" value={search} onChange={e => setSearch(e.target.value)}
style={{ background: "#111c2e", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 5, padding: "7px 12px", fontSize: 12, fontFamily: "inherit", width: 220 }} />
{[
["Tribe", filterTribe, setFilterTribe, ["All", ...TRIBES]],
["Env", filterEnv, setFilterEnv, ["All", "Prod", "NonProd"]],
["Status", filterStatus, setFilterStatus, ["All", ...allStatuses]],
].map(([label, val, setter, opts]) => (
<div key={label} style={{ display: "flex", alignItems: "center", gap: 6 }}>
<span style={{ fontSize: 11, color: "#4a7aaa", letterSpacing: "0.06em" }}>{label}</span>
<select value={val} onChange={e => setter(e.target.value)}
style={{ background: "#111c2e", border: "1px solid #1e3a5f", color: "#cde", borderRadius: 4, padding: "6px 10px", fontSize: 12, fontFamily: "inherit" }}>
{opts.map(o => <option key={o} value={o}>{o || "— none —"}</option>)}
</select>
</div>
))}
<span style={{ marginLeft: "auto", fontSize: 11, color: "#3a6a9a" }}>
{filtered.length} of {data.length} instances
</span>
</div>
{/* Table */}
<div style={{ overflowX: "auto", borderRadius: 8, border: "1px solid #1a3050" }}>
<table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12 }}>
<thead>
<tr style={{ background: "#0a1a2e", borderBottom: "1px solid #1e3a5f" }}>
{["#", "RDS Instance Name", "Tribe", "Environment", "Status", "Notes", "Actions"].map(h => (
<th key={h} style={{
padding: "10px 12px", textAlign: "left", color: "#4a8ab5",
fontWeight: 600, fontSize: 10, letterSpacing: "0.1em",
textTransform: "uppercase", whiteSpace: "nowrap",
borderRight: "1px solid #111c2e",
}}>{h}</th>
))}
</tr>
</thead>
<tbody>
{filtered.map((row, idx) => (
<tr key={row.id} style={{
background: idx % 2 === 0 ? "#0d0d0d" : "#0f1a28",
borderBottom: "1px solid #141e2e",
transition: "background 0.15s",
}}
onMouseEnter={e => e.currentTarget.style.background = "#111c2e"}
onMouseLeave={e => e.currentTarget.style.background = idx % 2 === 0 ? "#0d0d0d" : "#0f1a28"}>
<td style={{ padding: "8px 12px", color: "#3a6a9a", fontFamily: "'IBM Plex Mono', monospace", fontSize: 11 }}>{idx + 1}</td>
<td style={{ padding: "8px 12px", color: "#b8d4f0", fontFamily: "'IBM Plex Mono', monospace", fontSize: 11 }}>{row.name}</td>
{/* Tribe */}
<td style={{ padding: "8px 12px" }}>
{editingId === row.id ? (
<select value={editValues.tribe} onChange={e => setEditValues(p => ({ ...p, tribe: e.target.value }))}
style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "inherit" }}>
{TRIBES.map(t => <option key={t}>{t}</option>)}
</select>
) : <span style={{ color: "#9ab8d8", fontSize: 12 }}>{row.tribe}</span>}
</td>
{/* Environment */}
<td style={{ padding: "8px 12px" }}>
{editingId === row.id ? (
<select value={editValues.environment} onChange={e => setEditValues(p => ({ ...p, environment: e.target.value }))}
style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "inherit" }}>
<option>Prod</option><option>NonProd</option>
</select>
) : <EnvBadge env={row.environment} />}
</td>
{/* Status */}
<td style={{ padding: "8px 12px" }}>
{editingId === row.id ? (
<select value={editValues.status} onChange={e => setEditValues(p => ({ ...p, status: e.target.value }))}
style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "inherit" }}>
{STATUS_OPTIONS.map(s => <option key={s} value={s}>{s || "— none —"}</option>)}
</select>
) : <StatusBadge status={row.status} />}
</td>
{/* Notes */}
<td style={{ padding: "8px 12px", color: "#5a8aaa", fontSize: 11, fontStyle: "italic" }}>
{editingId === row.id ? (
<input value={editValues.notes} onChange={e => setEditValues(p => ({ ...p, notes: e.target.value }))}
style={{ background: "#0d1f3c", border: "1px solid #2a6ab5", color: "#cde", borderRadius: 3, padding: "3px 6px", fontSize: 11, fontFamily: "'IBM Plex Mono', monospace", width: 140 }} />
) : row.notes || ""}
</td>
{/* Actions */}
<td style={{ padding: "8px 12px", whiteSpace: "nowrap" }}>
{editingId === row.id ? (
<div style={{ display: "flex", gap: 6 }}>
<button onClick={() => saveEdit(row.id)} style={{ background: "#1a4a8a", color: "#7ec8f4", border: "none", borderRadius: 3, padding: "3px 10px", fontSize: 11, cursor: "pointer" }}>Save</button>
<button onClick={() => setEditingId(null)} style={{ background: "#1a2a3a", color: "#7aaa9a", border: "none", borderRadius: 3, padding: "3px 8px", fontSize: 11, cursor: "pointer" }}>✕</button>
</div>
) : (
<div style={{ display: "flex", gap: 6 }}>
<button onClick={() => startEdit(row)} style={{ background: "#0d1f3c", color: "#4a8ab5", border: "1px solid #1e3a5f", borderRadius: 3, padding: "3px 8px", fontSize: 11, cursor: "pointer" }}>Edit</button>
<button onClick={() => deleteRow(row.id)} style={{ background: "#1a0a0a", color: "#aa4444", border: "1px solid #3a1a1a", borderRadius: 3, padding: "3px 8px", fontSize: 11, cursor: "pointer" }}>Del</button>
</div>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)}
{activeTab === "summary" && (
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
{/* Status Summary */}
<div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
<h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>Overall Status</h3>
{Object.entries(summary).sort((a, b) => b[1] - a[1]).map(([status, count]) => {
const c = STATUS_COLORS[status] || STATUS_COLORS[""];
const pct = ((count / data.length) * 100).toFixed(1);
return (
<div key={status} style={{ marginBottom: 12 }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
<span style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 12 }}>
<span style={{ width: 8, height: 8, borderRadius: "50%", background: c.dot }} />
<span style={{ color: c.text }}>{status || "—"}</span>
</span>
<span style={{ color: "#7ec8f4", fontFamily: "'IBM Plex Mono', monospace", fontSize: 12 }}>
{count} <span style={{ color: "#3a6a9a" }}>({pct}%)</span>
</span>
</div>
<div style={{ height: 4, background: "#0d1f3c", borderRadius: 2 }}>
<div style={{ height: 4, width: `${pct}%`, background: c.dot, borderRadius: 2, transition: "width 0.5s" }} />
</div>
</div>
);
})}
</div>
{/* Tribe Breakdown */}
<div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
<h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>By Tribe</h3>
{tribeBreakdown.map(([tribe, count]) => {
const pct = ((count / data.length) * 100).toFixed(1);
return (
<div key={tribe} style={{ marginBottom: 10 }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 3 }}>
<span style={{ color: "#9ab8d8", fontSize: 12 }}>{tribe}</span>
<span style={{ color: "#7ec8f4", fontFamily: "'IBM Plex Mono', monospace", fontSize: 12 }}>
{count} <span style={{ color: "#3a6a9a" }}>({pct}%)</span>
</span>
</div>
<div style={{ height: 3, background: "#0d1f3c", borderRadius: 2 }}>
<div style={{ height: 3, width: `${pct}%`, background: "#2a6ab5", borderRadius: 2, transition: "width 0.5s" }} />
</div>
</div>
);
})}
</div>
{/* Env split */}
<div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
<h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>Prod vs NonProd</h3>
{["Prod", "NonProd"].map(env => {
const count = data.filter(r => r.environment === env).length;
const pct = ((count / data.length) * 100).toFixed(1);
const c = ENV_COLORS[env];
return (
<div key={env} style={{ marginBottom: 14 }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
<span style={{ color: c.text, fontSize: 13 }}>{env}</span>
<span style={{ color: "#7ec8f4", fontFamily: "'IBM Plex Mono', monospace", fontSize: 12 }}>
{count} <span style={{ color: "#3a6a9a" }}>({pct}%)</span>
</span>
</div>
<div style={{ height: 6, background: "#0d1f3c", borderRadius: 3 }}>
<div style={{ height: 6, width: `${pct}%`, background: c.border, borderRadius: 3 }} />
</div>
</div>
);
})}
</div>
{/* Quick stats */}
<div style={{ background: "#0a1628", border: "1px solid #1e3a5f", borderRadius: 8, padding: 20 }}>
<h3 style={{ margin: "0 0 16px", color: "#7ec8f4", fontSize: 13, letterSpacing: "0.08em", textTransform: "uppercase" }}>Quick Stats</h3>
{[
["Total Instances", data.length, "#7ec8f4"],
["Completed Upgrade", data.filter(r => r.status === "Completed Upgrade").length, "#4da6d8"],
["Decommed", data.filter(r => r.status === "Decommed").length, "#4caf50"],
["For Decomm", data.filter(r => r.status?.startsWith("For Decomm")).length, "#e6a817"],
["No Status Yet", data.filter(r => !r.status).length, "#888"],
].map(([label, val, color]) => (
<div key={label} style={{ display: "flex", justifyContent: "space-between", padding: "8px 0", borderBottom: "1px solid #111c2e" }}>
<span style={{ color: "#9ab8d8", fontSize: 12 }}>{label}</span>
<span style={{ color, fontFamily: "'IBM Plex Mono', monospace", fontSize: 14, fontWeight: 700 }}>{val}</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
}
import { useState, useMemo } from "react";
const STATUS_OPTIONS = [
"Completed Upgrade",
"Completed Traffic Swing",
"Decommed",
"For Decom (Housekeeping)",
"For Decom (Upgrade Not Needed)",
"For Decomm",
"Access",
"In Progress",
"Pending",
"",
];
const STATUS_COLORS = {
"Completed Upgrade": { bg: "#1a3a5c", text: "#7ec8f4", dot: "#4da6d8" },
"Completed Traffic Swing": { bg: "#0e2a40", text: "#5bbcf0", dot: "#3399cc" },
"Decommed": { bg: "#1a3d1a", text: "#7dcc7d", dot: "#4caf50" },
"For Decom (Housekeeping)": { bg: "#2a3d14", text: "#b5e07a", dot: "#8bc34a" },
"For Decom (Upgrade Not Needed)": { bg: "#3d2e00", text: "#f5c842", dot: "#e6a817" },
"For Decomm": { bg: "#3d2e00", text: "#f5c842", dot: "#e6a817" },
"Access": { bg: "#3d1a2e", text: "#f07ab5", dot: "#e91e8c" },
"In Progress": { bg: "#1a2a3d", text: "#80b3ff", dot: "#4488ff" },
"Pending": { bg: "#2d2d2d", text: "#aaaaaa", dot: "#888888" },
"": { bg: "#1e1e1e", text: "#666", dot: "#555" },
};
const ENV_COLORS = {
Prod: { bg: "#2a1a1a", text: "#ff7c7c", border: "#c0392b" },
NonProd: { bg: "#1a2a1a", text: "#7ccc7c", border: "#27ae60" },
};
const RAW_DATA = [
{ name: "amex-db-cluster4pro", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "amex-db-cluster4pro-replica", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "bifrost-prod", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "buyload-test", tribe: "B2C", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "cluster5-shared-db-uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "ctr4pushnotifsvcdbpro", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "ctr4pushnotifsvcdbpro-replica", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "db-optimization-prod", tribe: "DBOps", environment: "NonProd", status: "", notes: "" },
{ name: "devtools-perf", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "gloanrds-prd", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "gloanrds-prd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "golbat", tribe: "B2B", environment: "Prod", status: "Decommed", notes: "" },
{ name: "golbat-replica", tribe: "B2B", environment: "Prod", status: "Decommed", notes: "" },
{ name: "knowhere", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "konga", tribe: "Technology", environment: "Prod", status: "Access", notes: "" },
{ name: "konga-kong-cluster-production", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "konga-kong-cluster-uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "l2-ackerman", tribe: "L2", environment: "Prod", status: "", notes: "" },
{ name: "marvel", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "octdbprod", tribe: "Enterprise Services", environment: "Prod", status: "", notes: "" },
{ name: "octdbprod-old1", tribe: "Enterprise Services", environment: "Prod", status: "Decommed", notes: "" },
{ name: "partner-notifs-prd", tribe: "Lending", environment: "Prod", status: "Access", notes: "binlog_format (Mixed)" },
{ name: "partner-notifs-prd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "payments-pickaxe-production", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "payments-pickaxe-replica-production", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "promocode", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "promocode-replica", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "rcbc", tribe: "Funds", environment: "Prod", status: "", notes: "No objects" },
{ name: "rcbc-replica", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "sem-automation-rds", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "sendmoney-kkb-replica", tribe: "Funds", environment: "Prod", status: "For Decomm", notes: "" },
{ name: "sendmoney-perf", tribe: "Funds", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "shared-db-cluster1uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster1uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster2uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster2uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster4uat", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-db-cluster4uat-replica", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-rds-sit", tribe: "Technology", environment: "NonProd", status: "", notes: "" },
{ name: "shared-rds-sit-16-june-test", tribe: "Technology", environment: "NonProd", status: "For Decomm", notes: "" },
{ name: "sqlag-rds-uat", tribe: "DBOps", environment: "NonProd", status: "", notes: "MTCSD-287542" },
{ name: "telcoscore-db-prod", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "telcoscore-db-prod-replica", tribe: "New Business", environment: "Prod", status: "", notes: "" },
{ name: "telcoscore-db-uat", tribe: "New Business", environment: "NonProd", status: "", notes: "" },
{ name: "unicorn-rds-sit", tribe: "New Business", environment: "NonProd", status: "", notes: "" },
{ name: "user-consent-serv", tribe: "Customer and Integration", environment: "Prod", status: "", notes: "" },
{ name: "user-consent-serv-replica", tribe: "Customer and Integration", environment: "Prod", status: "", notes: "" },
{ name: "ussd", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "ussd-replica", tribe: "B2C", environment: "Prod", status: "", notes: "" },
{ name: "victoria-prd", tribe: "Lending", environment: "Prod", status: "Access", notes: "" },
{ name: "victoria-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "vmacqdpd01", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "vmacqdpd01-replica", tribe: "Funds", environment: "Prod", status: "", notes: "" },
{ name: "vmbaiddv01", tribe: "Funds", environment: "NonProd", status: "", notes: "" },
{ name: "vmcladpd01", tribe: "Segments", environment: "Prod", status: "", notes: "" },
{ name: "vmcladpd02", tribe: "Segments", environment: "Prod", status: "", notes: "" },
{ name: "vmcladut01", tribe: "Segments", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
{ name: "vmcladut02", tribe: "Segments", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
{ name: "vmpokappdpd03", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokappdpd03-replica", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdisdpd03", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdisdpd03-replica", tribe: "B2B", environment: "Prod", status: "", notes: "" },
{ name: "vmpokdispduat01", tribe: "B2B", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "vmpokdispduat01-old1", tribe: "B2B", environment: "NonProd", status: "Decommed", notes: "Done April 23" },
{ name: "vmpokpduat01", tribe: "B2B", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "vmrqrddv01", tribe: "Segments", environment: "NonProd", status: "", notes: "" },
{ name: "vmrqrdpd01", tribe: "Segments", environment: "Prod", status: "Access", notes: "" },
{ name: "wiirdsmysql", tribe: "Lending", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "wiirdsmysql-replica", tribe: "Lending", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 23" },
{ name: "wiirdsprd", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "wiirdsprd-replica", tribe: "Lending", environment: "Prod", status: "", notes: "" },
{ name: "wire-sms-production", tribe: "Technology", environment: "Prod", status: "", notes: "binlog_format (Mixed)" },
{ name: "wire-sms-production-replica", tribe: "Technology", environment: "Prod", status: "", notes: "" },
{ name: "xrecon-logs-uat", tribe: "Enterprise Services", environment: "NonProd", status: "Completed Upgrade", notes: "Done April 20" },
];
const TRIBES = [...new Set(RAW_DATA.map(r => r.tribe))].sort();
function StatusBadge({ status }) {
const c = STATUS_COLORS[status] || STATUS_COLORS[""];
return (
<span style={{
background: c.bg,
color: c.text,
border:
1px solid ${c.dot}33,borderRadius: 4,
padding: "2px 8px",
fontSize: 11,
fontFamily: "'IBM Plex Mono', monospace",
whiteSpace: "nowrap",
display: "inline-flex",
alignItems: "center",
gap: 5,
}}>
<span style={{ width: 6, height: 6, borderRadius: "50%", background: c.dot, flexShrink: 0 }} />
{status || "—"}
);
}
function EnvBadge({ env }) {
const c = ENV_COLORS[env] || { bg: "#222", text: "#aaa", border: "#555" };
return (
<span style={{
background: c.bg,
color: c.text,
border:
1px solid ${c.border}55,borderRadius: 3,
padding: "1px 7px",
fontSize: 11,
fontFamily: "'IBM Plex Mono', monospace",
letterSpacing: "0.03em",
}}>{env}
);
}
export default function App() {
const [data, setData] = useState(RAW_DATA.map((r, i) => ({ ...r, id: i })));
const [editingId, setEditingId] = useState(null);
const [editValues, setEditValues] = useState({});
const [filterTribe, setFilterTribe] = useState("All");
const [filterEnv, setFilterEnv] = useState("All");
const [filterStatus, setFilterStatus] = useState("All");
const [search, setSearch] = useState("");
const [showAddRow, setShowAddRow] = useState(false);
const [newRow, setNewRow] = useState({ name: "", tribe: "B2C", environment: "Prod", status: "", notes: "" });
const [activeTab, setActiveTab] = useState("table");
const filtered = useMemo(() => data.filter(r => {
if (filterTribe !== "All" && r.tribe !== filterTribe) return false;
if (filterEnv !== "All" && r.environment !== filterEnv) return false;
if (filterStatus !== "All" && r.status !== filterStatus) return false;
if (search && !r.name.toLowerCase().includes(search.toLowerCase()) && !r.tribe.toLowerCase().includes(search.toLowerCase())) return false;
return true;
}), [data, filterTribe, filterEnv, filterStatus, search]);
const summary = useMemo(() => {
const counts = {};
data.forEach(r => {
const k = r.status || "Pending";
counts[k] = (counts[k] || 0) + 1;
});
return counts;
}, [data]);
const tribeBreakdown = useMemo(() => {
const counts = {};
data.forEach(r => {
counts[r.tribe] = (counts[r.tribe] || 0) + 1;
});
return Object.entries(counts).sort((a, b) => b[1] - a[1]);
}, [data]);
function startEdit(row) {
setEditingId(row.id);
setEditValues({ status: row.status, notes: row.notes, tribe: row.tribe, environment: row.environment });
}
function saveEdit(id) {
setData(prev => prev.map(r => r.id === id ? { ...r, ...editValues } : r));
setEditingId(null);
}
function deleteRow(id) {
setData(prev => prev.filter(r => r.id !== id));
}
function addRow() {
if (!newRow.name.trim()) return;
setData(prev => [...prev, { ...newRow, id: Date.now() }]);
setNewRow({ name: "", tribe: "B2C", environment: "Prod", status: "", notes: "" });
setShowAddRow(false);
}
const allStatuses = [...new Set(data.map(r => r.status))].sort();
return (
<div style={{
minHeight: "100vh",
background: "#0d0d0d",
color: "#e0e0e0",
fontFamily: "'IBM Plex Sans', 'Segoe UI', sans-serif",
padding: "0",
}}>
{/* Header */}
<div style={{
background: "linear-gradient(135deg, #0a1628 0%, #0d1f3c 50%, #091a2e 100%)",
borderBottom: "1px solid #1e3a5f",
padding: "20px 28px 0",
}}>
<div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", marginBottom: 16 }}>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 4 }}>
<span style={{
background: "#1a4a8a",
color: "#7ec8f4",
fontSize: 10,
padding: "2px 8px",
borderRadius: 2,
letterSpacing: "0.12em",
fontFamily: "'IBM Plex Mono', monospace",
fontWeight: 600,
}}>MYSQL 8.4
<span style={{ color: "#3a6a9a", fontSize: 11 }}>RDS UPGRADE TRACKER
<h1 style={{
margin: 0,
fontSize: 22,
fontWeight: 700,
color: "#d4e9ff",
letterSpacing: "-0.01em",
}}>MySQL RDS Upgrade Dashboard
<p style={{ margin: "4px 0 0", fontSize: 12, color: "#4a7aaa" }}>
Total: <strong style={{ color: "#7ec8f4" }}>{data.length} instances tracked
<button
onClick={() => setShowAddRow(!showAddRow)}
style={{
background: "#1a4a8a",
color: "#7ec8f4",
border: "1px solid #2a6ab5",
borderRadius: 5,
padding: "7px 14px",
fontSize: 12,
cursor: "pointer",
fontFamily: "inherit",
display: "flex",
alignItems: "center",
gap: 6,
}}>
+ Add Instance
);
}