From 3104f83170cbcf1feb67da5ff44812ebc183f604 Mon Sep 17 00:00:00 2001 From: Artur Oliveira Date: Tue, 16 Dec 2025 18:57:11 -0300 Subject: [PATCH] =?UTF-8?q?feat(ui):=20adicionar=20suporte=20avan=C3=A7ado?= =?UTF-8?q?=20de=20tema=20-=20define=20tokens=20CSS/Tailwind=20com=20varia?= =?UTF-8?q?ntes=20claras=20e=20escuras=20-=20adapta=20layout,=20filtros,?= =?UTF-8?q?=20modais=20e=20tabela=20aos=20novos=20estilos=20-=20adiciona?= =?UTF-8?q?=20favicon=20otimizado=20e=20controles=20de=20dados=20sens?= =?UTF-8?q?=C3=ADveis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 4 +- frontend/public/favicon.png | Bin 0 -> 4659 bytes frontend/src/App.css | 30 +++++++- frontend/src/App.tsx | 23 +++--- frontend/src/components/Header.tsx | 2 +- frontend/src/components/ServerCardMetrics.tsx | 12 +-- frontend/src/components/ServersFilterBar.tsx | 19 ++++- frontend/src/components/ServersTable.tsx | 71 +++++++++--------- frontend/src/components/common/Modal.tsx | 12 +-- .../src/components/header/BulkImportModal.tsx | 26 +++---- .../src/components/header/HeaderActions.tsx | 8 +- .../src/components/header/HeaderBrand.tsx | 4 +- .../src/components/header/ProfileModal.tsx | 12 +-- .../src/components/header/ServerModal.tsx | 10 +-- frontend/src/pages/Dashboard.tsx | 10 ++- frontend/src/pages/Login.tsx | 19 ++--- frontend/tailwind.config.js | 24 ++++-- 17 files changed, 174 insertions(+), 112 deletions(-) create mode 100644 frontend/public/favicon.png diff --git a/frontend/index.html b/frontend/index.html index 7cfb423..0a76d12 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,9 +2,9 @@ - + - frontned + Hit Servers Manager
diff --git a/frontend/public/favicon.png b/frontend/public/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..4a9105c8f029ad996064e3c8d2dce776e12e9b13 GIT binary patch literal 4659 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 - - - - - } /> - } /> - - +
+ + + + + + } /> + } /> + + +
); } diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 9af1ab6..355990c 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -254,5 +254,5 @@ export const Header = ({ }; const Styles = { - wrapper: "flex items-center justify-between rounded-xl border border-cardBorder bg-card px-6 py-4 shadow-sm", + wrapper: "flex items-center justify-between rounded-xl border border-cardBorder bg-card px-6 py-4 shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/50", }; diff --git a/frontend/src/components/ServerCardMetrics.tsx b/frontend/src/components/ServerCardMetrics.tsx index aa980cd..9c271a9 100644 --- a/frontend/src/components/ServerCardMetrics.tsx +++ b/frontend/src/components/ServerCardMetrics.tsx @@ -74,11 +74,11 @@ export const ServerCardMetrics = () => { const Styles = { grid: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", - card: "flex items-center gap-4 rounded-xl border border-cardBorder bg-gradient-to-br from-white/90 to-card p-5 shadow-sm", - iconWrapper: "flex h-14 w-14 items-center justify-center rounded-lg border border-accent/20 bg-accent/10 text-accent", + card: "flex items-center gap-4 rounded-xl border border-cardBorder bg-gradient-to-br from-white/90 to-card p-5 shadow-sm dark:border-slate-800 dark:from-slate-900 dark:to-slate-950 dark:shadow-slate-900/40", + iconWrapper: "flex h-14 w-14 items-center justify-center rounded-lg border border-accent/20 bg-accent/10 text-accent dark:border-slate-800 dark:bg-slate-800/60", textGroup: "flex flex-col", - label: "text-xs font-medium uppercase tracking-wide text-text-secondary", - value: "text-3xl font-semibold text-text leading-tight", - placeholder: "p-4 rounded-lg border border-cardBorder bg-card text-text-secondary text-sm", - error: "p-4 rounded-lg border border-red-200 bg-red-50 text-red-600 text-sm", + label: "text-xs font-medium uppercase tracking-wide text-text-secondary dark:text-slate-400", + value: "text-3xl font-semibold leading-tight text-text dark:text-white", + placeholder: "rounded-lg border border-cardBorder bg-card p-4 text-sm text-text-secondary dark:border-slate-800 dark:bg-slate-900 dark:text-slate-400", + error: "rounded-lg border border-red-200 bg-red-50 p-4 text-sm text-red-600 dark:border-red-400/30 dark:bg-red-950/40 dark:text-red-300", }; diff --git a/frontend/src/components/ServersFilterBar.tsx b/frontend/src/components/ServersFilterBar.tsx index 06d81b6..cad9618 100644 --- a/frontend/src/components/ServersFilterBar.tsx +++ b/frontend/src/components/ServersFilterBar.tsx @@ -15,6 +15,8 @@ interface Props { serverTypeOptions?: ServersType[]; applicationOptions?: Applications[]; databaseOptions?: DatabaseType[]; + hideSensitive: boolean; + onToggleSensitive: () => void; } const withAllOption = (options?: T[]): Array => { @@ -37,6 +39,8 @@ export const ServersFilterBar = ({ serverTypeOptions, applicationOptions, databaseOptions, + hideSensitive, + onToggleSensitive, }: Props) => { const typeOptions = withAllOption(serverTypeOptions); const applicationOptionsList = withAllOption(applicationOptions); @@ -73,6 +77,11 @@ export const ServersFilterBar = ({ onChange={(event) => onDbTypeChange(event.target.value as DatabaseType | OptionAll)} options={databaseOptionsList} /> +
+ +
); }; @@ -100,10 +109,12 @@ const Select = ({ label, value, onChange, options }: SelectPro }; const Styles = { - wrapper: "flex flex-wrap gap-4 rounded-lg border border-cardBorder bg-white/70 p-4 shadow-sm", + wrapper: "flex flex-wrap items-end gap-4 rounded-lg border border-cardBorder bg-white/70 p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/40", searchGroup: "flex-1 min-w-[220px]", selectGroup: "flex min-w-[150px] flex-col gap-1", - label: "text-xs font-semibold uppercase tracking-wide text-text-secondary", - input: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent", - select: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize", + label: "text-xs font-semibold uppercase tracking-wide text-text-secondary dark:text-slate-400", + input: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 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", + select: "w-full rounded-lg border border-cardBorder bg-white px-3 py-2 text-sm text-text outline-none focus:border-accent focus:ring-1 focus:ring-accent capitalize dark:border-slate-700 dark:bg-slate-900 dark:text-slate-100", + toggleWrapper: "flex min-w-[200px] items-center justify-end", + toggleButton: "rounded-md border border-cardBorder px-4 py-2 text-xs font-semibold uppercase tracking-wide text-text-secondary hover:bg-bg dark:border-slate-700 dark:text-slate-300 dark:hover:bg-slate-800", }; diff --git a/frontend/src/components/ServersTable.tsx b/frontend/src/components/ServersTable.tsx index 9844099..51d59e8 100644 --- a/frontend/src/components/ServersTable.tsx +++ b/frontend/src/components/ServersTable.tsx @@ -1,4 +1,3 @@ -import { useState } from "react"; import type { Server } from "../types/Server"; interface Props { @@ -9,6 +8,7 @@ interface Props { pageSize: number; totalPages: number; totalItems: number; + hideSensitive: boolean; onPageChange: (page: number) => void; onPageSizeChange: (size: number) => void; } @@ -23,38 +23,36 @@ export const ServersTable = ({ pageSize, totalPages, totalItems, + hideSensitive, onPageChange, onPageSizeChange, }: Props) => { - const [hideSensitive, setHideSensitive] = useState(false); const showingFrom = totalItems === 0 ? 0 : page * pageSize + 1; const showingTo = totalItems === 0 ? 0 : Math.min((page + 1) * pageSize, totalItems); const hasResults = servers.length > 0; + const isInitialLoad = loading && !hasResults; + const showOverlay = loading && hasResults; const formatSensitiveValue = (value: string | number) => (hideSensitive ? "••••" : value); return (
- - {loading &&
Carregando servidores...
} + {isInitialLoad &&
Carregando servidores...
} {error &&
{error}
} {!loading && !error && servers.length === 0 && (
Nenhum servidor encontrado.
)} - {!loading && !error && hasResults && ( - <> + {hasResults && ( +
+ {showOverlay && ( +
+ Atualizando lista... +
+ )}
- + @@ -69,14 +67,14 @@ export const ServersTable = ({ {servers.map((server) => ( @@ -130,29 +128,34 @@ export const ServersTable = ({ - + )} +
); }; const Styles = { - card: "relative bg-card border border-cardBorder shadow-sm rounded-lg overflow-hidden", - status: "p-4 text-text-secondary text-sm", - error: "p-4 text-red-600 text-sm", - tableWrapper: "overflow-x-auto rounded-lg shadow-sm border border-cardBorder", - table: "min-w-full divide-y divide-cardBorder table-auto", - tableHead: "bg-gray-50 sticky top-0", - tableHeadCell: "px-4 py-3 text-left text-xs font-semibold uppercase tracking-wide text-text-secondary", - tableBody: "bg-white divide-y divide-cardBorder", - rowCell: "px-4 py-3 text-sm text-text", - pagination: "flex flex-col gap-2 border-t border-cardBorder bg-white px-4 py-3 sm:flex-row sm:items-center sm:justify-between", - pageInfo: "text-sm text-text-secondary", + 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", paginationControls: "flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-4", - pageSizeLabel: "text-sm text-text 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", + 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", pageButtons: "flex items-center gap-3", - pageButton: "rounded-md border border-cardBorder px-3 py-1.5 text-sm font-medium text-text hover:bg-bg disabled:opacity-50 disabled:hover:bg-transparent", - pageIndicator: "text-sm text-text-secondary", - hideButton: "absolute right-3 top-3 rounded-md border border-cardBorder px-3 py-1.5 text-xs font-semibold uppercase tracking-wide text-text-secondary hover:bg-bg disabled:opacity-50 disabled:hover:bg-transparent bg-white shadow-sm", + 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", }; diff --git a/frontend/src/components/common/Modal.tsx b/frontend/src/components/common/Modal.tsx index f41e470..14012f7 100644 --- a/frontend/src/components/common/Modal.tsx +++ b/frontend/src/components/common/Modal.tsx @@ -64,11 +64,11 @@ export const Modal = ({ isOpen, title, onClose, children, description, bodyClass }; const Styles = { - overlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 backdrop-blur-sm px-4 !mt-0", - dialog: "w-full max-w-2xl rounded-2xl border border-cardBorder bg-card p-6 shadow-xl transform transition-all duration-200 animate-fade-up", - header: "flex items-start justify-between gap-4 pb-4 border-b border-cardBorder", - title: "text-lg font-semibold text-text", - closeButton: "text-2xl leading-none text-text-secondary hover:text-text", - description: "pt-3 text-sm text-text-secondary", + overlay: "fixed inset-0 z-40 flex items-center justify-center bg-black/40 px-4 backdrop-blur-sm !mt-0 dark:bg-black/60", + dialog: "w-full max-w-2xl transform animate-fade-up rounded-2xl border border-cardBorder bg-card p-6 shadow-xl transition-all duration-200 dark:border-slate-800 dark:bg-slate-900 dark:shadow-slate-900/70", + header: "flex items-start justify-between gap-4 border-b border-cardBorder pb-4 dark:border-slate-800", + title: "text-lg font-semibold text-text dark:text-white", + closeButton: "text-2xl leading-none text-text-secondary hover:text-text dark:text-slate-400 dark:hover:text-white", + description: "pt-3 text-sm text-text-secondary dark:text-slate-400", body: "pt-4", }; diff --git a/frontend/src/components/header/BulkImportModal.tsx b/frontend/src/components/header/BulkImportModal.tsx index 6b09733..0d50b0c 100644 --- a/frontend/src/components/header/BulkImportModal.tsx +++ b/frontend/src/components/header/BulkImportModal.tsx @@ -34,7 +34,7 @@ export const BulkImportModal = ({
Nome IP Porta
{server.name} {formatSensitiveValue(server.ip)} {formatSensitiveValue(server.port)} {formatSensitiveValue(server.user)} - + {hideSensitive ? "••••" : server.password}