2025-12-16 15:52:32 +00:00
|
|
|
import type { Server } from "../types/Server";
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
servers: Server[];
|
|
|
|
|
loading: boolean;
|
|
|
|
|
error: string | null;
|
2025-12-16 21:16:22 +00:00
|
|
|
page: number;
|
|
|
|
|
pageSize: number;
|
|
|
|
|
totalPages: number;
|
|
|
|
|
totalItems: number;
|
2025-12-16 21:57:11 +00:00
|
|
|
hideSensitive: boolean;
|
2025-12-16 21:16:22 +00:00
|
|
|
onPageChange: (page: number) => void;
|
|
|
|
|
onPageSizeChange: (size: number) => void;
|
2025-12-16 15:52:32 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-16 21:16:22 +00:00
|
|
|
const PAGE_SIZE_OPTIONS = [5, 10, 20, 50];
|
|
|
|
|
|
|
|
|
|
export const ServersTable = ({
|
|
|
|
|
servers,
|
|
|
|
|
loading,
|
|
|
|
|
error,
|
|
|
|
|
page,
|
|
|
|
|
pageSize,
|
|
|
|
|
totalPages,
|
|
|
|
|
totalItems,
|
2025-12-16 21:57:11 +00:00
|
|
|
hideSensitive,
|
2025-12-16 21:16:22 +00:00
|
|
|
onPageChange,
|
|
|
|
|
onPageSizeChange,
|
|
|
|
|
}: Props) => {
|
|
|
|
|
const showingFrom = totalItems === 0 ? 0 : page * pageSize + 1;
|
|
|
|
|
const showingTo = totalItems === 0 ? 0 : Math.min((page + 1) * pageSize, totalItems);
|
|
|
|
|
const hasResults = servers.length > 0;
|
2025-12-16 21:57:11 +00:00
|
|
|
const isInitialLoad = loading && !hasResults;
|
|
|
|
|
const showOverlay = loading && hasResults;
|
2025-12-16 21:16:22 +00:00
|
|
|
|
2025-12-16 21:19:14 +00:00
|
|
|
const formatSensitiveValue = (value: string | number) => (hideSensitive ? "••••" : value);
|
|
|
|
|
|
2025-12-16 15:52:32 +00:00
|
|
|
return (
|
|
|
|
|
<div className={Styles.card}>
|
2025-12-16 21:57:11 +00:00
|
|
|
{isInitialLoad && <div className={Styles.status}>Carregando servidores...</div>}
|
2025-12-16 15:52:32 +00:00
|
|
|
{error && <div className={Styles.error}>{error}</div>}
|
|
|
|
|
{!loading && !error && servers.length === 0 && (
|
|
|
|
|
<div className={Styles.status}>Nenhum servidor encontrado.</div>
|
|
|
|
|
)}
|
2025-12-16 21:57:11 +00:00
|
|
|
{hasResults && (
|
|
|
|
|
<div className="relative space-y-4">
|
|
|
|
|
{showOverlay && (
|
|
|
|
|
<div className={Styles.loadingOverlay}>
|
|
|
|
|
<span>Atualizando lista...</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
2025-12-16 21:16:22 +00:00
|
|
|
<div className={Styles.tableWrapper}>
|
|
|
|
|
<table className={Styles.table}>
|
|
|
|
|
<thead className={Styles.tableHead}>
|
2025-12-16 21:57:11 +00:00
|
|
|
<tr className={Styles.tableHeadRow}>
|
2025-12-16 21:16:22 +00:00
|
|
|
<th className={Styles.tableHeadCell}>Nome</th>
|
|
|
|
|
<th className={Styles.tableHeadCell}>IP</th>
|
|
|
|
|
<th className={Styles.tableHeadCell}>Porta</th>
|
|
|
|
|
<th className={Styles.tableHeadCell}>Usuário</th>
|
|
|
|
|
<th className={Styles.tableHeadCell}>Senha</th>
|
|
|
|
|
<th className={Styles.tableHeadCell}>Tipo</th>
|
|
|
|
|
<th className={Styles.tableHeadCell}>Aplicação</th>
|
|
|
|
|
<th className={Styles.tableHeadCell}>Banco</th>
|
2025-12-16 15:52:32 +00:00
|
|
|
</tr>
|
2025-12-16 21:16:22 +00:00
|
|
|
</thead>
|
|
|
|
|
<tbody className={Styles.tableBody}>
|
|
|
|
|
{servers.map((server) => (
|
|
|
|
|
<tr
|
|
|
|
|
key={server.id}
|
2025-12-16 21:57:11 +00:00
|
|
|
className={Styles.tableRow}
|
2025-12-16 21:16:22 +00:00
|
|
|
>
|
2025-12-16 21:19:14 +00:00
|
|
|
<td className={Styles.rowCell}>{server.name}</td>
|
|
|
|
|
<td className={Styles.rowCell}>{formatSensitiveValue(server.ip)}</td>
|
|
|
|
|
<td className={Styles.rowCell}>{formatSensitiveValue(server.port)}</td>
|
|
|
|
|
<td className={Styles.rowCell}>{formatSensitiveValue(server.user)}</td>
|
|
|
|
|
<td className={Styles.rowCell}>
|
2025-12-16 21:57:11 +00:00
|
|
|
<code className={Styles.password}>
|
2025-12-16 21:19:14 +00:00
|
|
|
{hideSensitive ? "••••" : server.password}
|
|
|
|
|
</code>
|
|
|
|
|
</td>
|
|
|
|
|
<td className={`${Styles.rowCell} capitalize`}>{server.type.toLowerCase()}</td>
|
|
|
|
|
<td className={`${Styles.rowCell} capitalize`}>{server.application.toLowerCase()}</td>
|
2025-12-16 21:16:22 +00:00
|
|
|
<td className={`${Styles.rowCell} capitalize`}>{server.dbType.toLowerCase()}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
))}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={Styles.pagination}>
|
|
|
|
|
<div className={Styles.pageInfo}>
|
|
|
|
|
Mostrando {showingFrom} - {showingTo} de {totalItems}
|
|
|
|
|
</div>
|
|
|
|
|
<div className={Styles.paginationControls}>
|
|
|
|
|
<label className={Styles.pageSizeLabel}>
|
|
|
|
|
Linhas por página
|
|
|
|
|
<select
|
|
|
|
|
className={Styles.pageSizeSelect}
|
|
|
|
|
value={pageSize}
|
|
|
|
|
onChange={(event) => onPageSizeChange(Number(event.target.value))}
|
|
|
|
|
>
|
|
|
|
|
{PAGE_SIZE_OPTIONS.map((option) => (
|
|
|
|
|
<option key={option} value={option}>
|
|
|
|
|
{option}
|
|
|
|
|
</option>
|
|
|
|
|
))}
|
|
|
|
|
</select>
|
|
|
|
|
</label>
|
|
|
|
|
<div className={Styles.pageButtons}>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className={Styles.pageButton}
|
|
|
|
|
onClick={() => onPageChange(page - 1)}
|
|
|
|
|
disabled={page <= 0}
|
|
|
|
|
>
|
|
|
|
|
Anterior
|
|
|
|
|
</button>
|
|
|
|
|
<span className={Styles.pageIndicator}>
|
|
|
|
|
Página {page + 1} de {Math.max(totalPages, 1)}
|
|
|
|
|
</span>
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
className={Styles.pageButton}
|
|
|
|
|
onClick={() => onPageChange(page + 1)}
|
|
|
|
|
disabled={totalPages === 0 || page >= totalPages - 1}
|
|
|
|
|
>
|
|
|
|
|
Próxima
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-12-16 21:57:11 +00:00
|
|
|
</div>
|
2025-12-16 15:52:32 +00:00
|
|
|
)}
|
2025-12-16 21:57:11 +00:00
|
|
|
<div className={Styles.footerSpacer} aria-hidden />
|
2025-12-16 15:52:32 +00:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const Styles = {
|
2025-12-16 21:57:11 +00:00
|
|
|
card: "overflow-hidden rounded-lg border border-cardBorder bg-card shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/40",
|
|
|
|
|
status: "p-4 text-sm text-text-secondary dark:text-slate-400",
|
|
|
|
|
error: "p-4 text-sm text-red-600 dark:text-red-400",
|
|
|
|
|
tableWrapper: "overflow-x-auto rounded-lg border border-cardBorder shadow-sm dark:border-slate-800 dark:bg-slate-950/40",
|
|
|
|
|
table: "min-w-full table-auto divide-y divide-cardBorder dark:divide-slate-800",
|
|
|
|
|
tableHead: "sticky top-0 bg-gray-50 dark:bg-slate-800",
|
|
|
|
|
tableHeadRow: "text-left text-text dark:text-slate-200",
|
|
|
|
|
tableHeadCell: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-text-secondary dark:text-slate-300",
|
|
|
|
|
tableBody: "divide-y divide-cardBorder bg-white dark:divide-slate-800 dark:bg-slate-900",
|
|
|
|
|
tableRow: "transition-colors even:bg-gray-50/50 hover:bg-gray-50 dark:even:bg-slate-900 dark:hover:bg-slate-800",
|
|
|
|
|
rowCell: "px-4 py-3 text-sm text-text dark:text-slate-100",
|
|
|
|
|
password: "rounded bg-gray-100 px-2 py-1 text-xs dark:bg-slate-800 dark:text-slate-100",
|
|
|
|
|
pagination: "flex flex-col gap-2 border-t border-cardBorder bg-white px-4 py-3 dark:border-slate-800 dark:bg-slate-900 sm:flex-row sm:items-center sm:justify-between",
|
|
|
|
|
pageInfo: "text-sm text-text-secondary dark:text-slate-400",
|
2025-12-16 21:16:22 +00:00
|
|
|
paginationControls: "flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4",
|
2025-12-16 21:57:11 +00:00
|
|
|
pageSizeLabel: "text-sm text-text dark:text-slate-100 flex items-center gap-2",
|
|
|
|
|
pageSizeSelect: "rounded-md border border-cardBorder bg-white px-2 py-1 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100",
|
2025-12-16 21:16:22 +00:00
|
|
|
pageButtons: "flex items-center gap-3",
|
2025-12-16 21:57:11 +00:00
|
|
|
pageButton: "rounded-md border border-cardBorder px-3 py-1.5 text-sm font-medium text-text transition-colors hover:bg-bg disabled:opacity-50 disabled:hover:bg-transparent dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-800",
|
|
|
|
|
pageIndicator: "text-sm text-text-secondary dark:text-slate-400",
|
|
|
|
|
footerSpacer: "h-4",
|
|
|
|
|
loadingOverlay: "absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-white/70 text-sm font-medium text-text backdrop-blur-sm dark:bg-slate-900/80 dark:text-slate-100",
|
2025-12-16 15:52:32 +00:00
|
|
|
};
|