Spaces:
Running
Running
Guilherme Silberfarb Costa commited on
Commit ·
3dc38bc
1
Parent(s): 918e0df
correcao de logs
Browse files- frontend/src/App.jsx +143 -99
- frontend/src/styles.css +52 -2
frontend/src/App.jsx
CHANGED
|
@@ -6,6 +6,8 @@ import PesquisaTab from './components/PesquisaTab'
|
|
| 6 |
import RepositorioTab from './components/RepositorioTab'
|
| 7 |
import VisualizacaoTab from './components/VisualizacaoTab'
|
| 8 |
|
|
|
|
|
|
|
| 9 |
const TABS = [
|
| 10 |
{ key: 'Início', label: 'Início', hint: 'Visão geral rápida do aplicativo' },
|
| 11 |
{ key: 'Pesquisa', label: 'Pesquisa', hint: 'Busca inicial de modelos .dai' },
|
|
@@ -33,11 +35,17 @@ export default function App() {
|
|
| 33 |
const [logsError, setLogsError] = useState('')
|
| 34 |
const [logsScope, setLogsScope] = useState('')
|
| 35 |
const [logsUsuario, setLogsUsuario] = useState('')
|
| 36 |
-
const [
|
| 37 |
|
| 38 |
const isAdmin = String(authUser?.perfil || '').toLowerCase() === 'admin'
|
| 39 |
const logsEnabled = Boolean(logsStatus?.enabled)
|
| 40 |
const logsDisabledReason = String(logsStatus?.reason || 'Logs indisponíveis')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
function resetToLogin(message = '') {
|
| 43 |
setAuthToken('')
|
|
@@ -50,6 +58,7 @@ export default function App() {
|
|
| 50 |
setLogsOpen(false)
|
| 51 |
setLogsEvents([])
|
| 52 |
setLogsError('')
|
|
|
|
| 53 |
setActiveTab(TABS[0].key)
|
| 54 |
setAuthError(message)
|
| 55 |
}
|
|
@@ -131,6 +140,7 @@ export default function App() {
|
|
| 131 |
setLogsOpen(false)
|
| 132 |
setLogsEvents([])
|
| 133 |
setLogsError('')
|
|
|
|
| 134 |
setLogsStatusLoading(false)
|
| 135 |
setLogsLoading(false)
|
| 136 |
return
|
|
@@ -138,6 +148,16 @@ export default function App() {
|
|
| 138 |
void carregarLogsStatus()
|
| 139 |
}, [authUser, isAdmin])
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
async function onSubmitLogin(event) {
|
| 142 |
event.preventDefault()
|
| 143 |
setAuthError('')
|
|
@@ -191,11 +211,13 @@ export default function App() {
|
|
| 191 |
setLogsLoading(true)
|
| 192 |
setLogsError('')
|
| 193 |
try {
|
| 194 |
-
const resp = await api.logsEvents({ scope: logsScope, usuario: logsUsuario, limit:
|
| 195 |
setLogsEvents(Array.isArray(resp?.events) ? resp.events : [])
|
|
|
|
| 196 |
} catch (err) {
|
| 197 |
setLogsError(err.message || 'Falha ao carregar logs.')
|
| 198 |
setLogsEvents([])
|
|
|
|
| 199 |
} finally {
|
| 200 |
setLogsLoading(false)
|
| 201 |
}
|
|
@@ -212,6 +234,10 @@ export default function App() {
|
|
| 212 |
await carregarEventosLogs()
|
| 213 |
}
|
| 214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
return (
|
| 216 |
<div className="app-shell">
|
| 217 |
<header className="app-header app-header-logo-only">
|
|
@@ -230,10 +256,10 @@ export default function App() {
|
|
| 230 |
<button
|
| 231 |
type="button"
|
| 232 |
onClick={() => void onToggleLogs()}
|
| 233 |
-
disabled={logsStatusLoading || !logsEnabled}
|
| 234 |
-
title={!logsEnabled ? logsDisabledReason : 'Abrir leitura de logs'}
|
| 235 |
>
|
| 236 |
-
Logs
|
| 237 |
</button>
|
| 238 |
) : null}
|
| 239 |
<button type="button" onClick={() => void onLogout()}>
|
|
@@ -243,98 +269,6 @@ export default function App() {
|
|
| 243 |
</div>
|
| 244 |
) : null}
|
| 245 |
|
| 246 |
-
{authUser && isAdmin && logsOpen ? (
|
| 247 |
-
<section className="logs-panel">
|
| 248 |
-
<div className="logs-panel-head">
|
| 249 |
-
<h3>Logs</h3>
|
| 250 |
-
<div className="logs-panel-meta">
|
| 251 |
-
<span>{logsStatus?.backend === 'hf_dataset' ? 'Origem: Dataset HF' : 'Origem: indisponível'}</span>
|
| 252 |
-
{logsStatus?.revision ? <span>Revisão: {String(logsStatus.revision).slice(0, 8)}</span> : null}
|
| 253 |
-
</div>
|
| 254 |
-
</div>
|
| 255 |
-
|
| 256 |
-
{!logsEnabled ? (
|
| 257 |
-
<div className="section1-empty-hint">{logsDisabledReason}</div>
|
| 258 |
-
) : (
|
| 259 |
-
<>
|
| 260 |
-
<div className="logs-filters">
|
| 261 |
-
<div className="logs-field">
|
| 262 |
-
<label htmlFor="logs-scope">Escopo</label>
|
| 263 |
-
<select id="logs-scope" value={logsScope} onChange={(event) => setLogsScope(event.target.value)}>
|
| 264 |
-
<option value="">Todos</option>
|
| 265 |
-
<option value="auth">Auth</option>
|
| 266 |
-
<option value="repositorio">Repositório</option>
|
| 267 |
-
<option value="elaboracao">Elaboração</option>
|
| 268 |
-
<option value="visualizacao">Visualização</option>
|
| 269 |
-
</select>
|
| 270 |
-
</div>
|
| 271 |
-
<div className="logs-field">
|
| 272 |
-
<label htmlFor="logs-usuario">Usuário</label>
|
| 273 |
-
<input
|
| 274 |
-
id="logs-usuario"
|
| 275 |
-
type="text"
|
| 276 |
-
value={logsUsuario}
|
| 277 |
-
onChange={(event) => setLogsUsuario(event.target.value)}
|
| 278 |
-
placeholder="filtrar por usuário"
|
| 279 |
-
autoComplete="off"
|
| 280 |
-
/>
|
| 281 |
-
</div>
|
| 282 |
-
<div className="logs-field logs-field-small">
|
| 283 |
-
<label htmlFor="logs-limit">Limite</label>
|
| 284 |
-
<input
|
| 285 |
-
id="logs-limit"
|
| 286 |
-
type="number"
|
| 287 |
-
min="1"
|
| 288 |
-
max="1000"
|
| 289 |
-
value={logsLimit}
|
| 290 |
-
onChange={(event) => setLogsLimit(Number(event.target.value || 200))}
|
| 291 |
-
/>
|
| 292 |
-
</div>
|
| 293 |
-
<button type="button" onClick={() => void carregarEventosLogs()} disabled={logsLoading}>
|
| 294 |
-
Atualizar
|
| 295 |
-
</button>
|
| 296 |
-
</div>
|
| 297 |
-
|
| 298 |
-
{logsError ? <div className="error-line">{logsError}</div> : null}
|
| 299 |
-
|
| 300 |
-
<div className="table-container">
|
| 301 |
-
<table className="logs-table">
|
| 302 |
-
<thead>
|
| 303 |
-
<tr>
|
| 304 |
-
<th>Timestamp</th>
|
| 305 |
-
<th>Usuário</th>
|
| 306 |
-
<th>Perfil</th>
|
| 307 |
-
<th>Escopo</th>
|
| 308 |
-
<th>Ação</th>
|
| 309 |
-
<th>Status</th>
|
| 310 |
-
<th>Detalhes</th>
|
| 311 |
-
</tr>
|
| 312 |
-
</thead>
|
| 313 |
-
<tbody>
|
| 314 |
-
{logsEvents.map((item) => (
|
| 315 |
-
<tr key={item.event_id || `${item.ts}-${item.action}`}>
|
| 316 |
-
<td>{item.ts || '-'}</td>
|
| 317 |
-
<td>{item.usuario || '-'}</td>
|
| 318 |
-
<td>{item.perfil || '-'}</td>
|
| 319 |
-
<td>{item.scope || '-'}</td>
|
| 320 |
-
<td>{item.action || '-'}</td>
|
| 321 |
-
<td>{item.status || '-'}</td>
|
| 322 |
-
<td className="logs-table-details">{JSON.stringify(item.details || {})}</td>
|
| 323 |
-
</tr>
|
| 324 |
-
))}
|
| 325 |
-
{!logsEvents.length ? (
|
| 326 |
-
<tr>
|
| 327 |
-
<td colSpan={7}>{logsLoading ? 'Carregando logs...' : 'Nenhum log encontrado para os filtros.'}</td>
|
| 328 |
-
</tr>
|
| 329 |
-
) : null}
|
| 330 |
-
</tbody>
|
| 331 |
-
</table>
|
| 332 |
-
</div>
|
| 333 |
-
</>
|
| 334 |
-
)}
|
| 335 |
-
</section>
|
| 336 |
-
) : null}
|
| 337 |
-
|
| 338 |
{authLoading ? <div className="status-line">Validando autenticação...</div> : null}
|
| 339 |
|
| 340 |
{!authLoading && !authUser ? (
|
|
@@ -377,7 +311,116 @@ export default function App() {
|
|
| 377 |
) : null}
|
| 378 |
|
| 379 |
{authUser ? (
|
| 380 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
<nav className="tabs" aria-label="Navegação principal">
|
| 382 |
{TABS.map((tab) => {
|
| 383 |
const active = tab.key === activeTab
|
|
@@ -416,7 +459,8 @@ export default function App() {
|
|
| 416 |
<div className="tab-pane" hidden={activeTab !== 'Repositório'}>
|
| 417 |
<RepositorioTab authUser={authUser} />
|
| 418 |
</div>
|
| 419 |
-
|
|
|
|
| 420 |
) : null}
|
| 421 |
</div>
|
| 422 |
)
|
|
|
|
| 6 |
import RepositorioTab from './components/RepositorioTab'
|
| 7 |
import VisualizacaoTab from './components/VisualizacaoTab'
|
| 8 |
|
| 9 |
+
const LOGS_PAGE_SIZE = 30
|
| 10 |
+
|
| 11 |
const TABS = [
|
| 12 |
{ key: 'Início', label: 'Início', hint: 'Visão geral rápida do aplicativo' },
|
| 13 |
{ key: 'Pesquisa', label: 'Pesquisa', hint: 'Busca inicial de modelos .dai' },
|
|
|
|
| 35 |
const [logsError, setLogsError] = useState('')
|
| 36 |
const [logsScope, setLogsScope] = useState('')
|
| 37 |
const [logsUsuario, setLogsUsuario] = useState('')
|
| 38 |
+
const [logsPage, setLogsPage] = useState(1)
|
| 39 |
|
| 40 |
const isAdmin = String(authUser?.perfil || '').toLowerCase() === 'admin'
|
| 41 |
const logsEnabled = Boolean(logsStatus?.enabled)
|
| 42 |
const logsDisabledReason = String(logsStatus?.reason || 'Logs indisponíveis')
|
| 43 |
+
const logsViewActive = Boolean(authUser && isAdmin && logsOpen)
|
| 44 |
+
const logsTotalPages = Math.max(1, Math.ceil(logsEvents.length / LOGS_PAGE_SIZE))
|
| 45 |
+
const logsCurrentPage = Math.min(logsPage, logsTotalPages)
|
| 46 |
+
const logsStartIndex = logsEvents.length ? (logsCurrentPage - 1) * LOGS_PAGE_SIZE : 0
|
| 47 |
+
const logsVisibleEvents = logsEvents.slice(logsStartIndex, logsStartIndex + LOGS_PAGE_SIZE)
|
| 48 |
+
const logsEndIndex = logsEvents.length ? Math.min(logsStartIndex + LOGS_PAGE_SIZE, logsEvents.length) : 0
|
| 49 |
|
| 50 |
function resetToLogin(message = '') {
|
| 51 |
setAuthToken('')
|
|
|
|
| 58 |
setLogsOpen(false)
|
| 59 |
setLogsEvents([])
|
| 60 |
setLogsError('')
|
| 61 |
+
setLogsPage(1)
|
| 62 |
setActiveTab(TABS[0].key)
|
| 63 |
setAuthError(message)
|
| 64 |
}
|
|
|
|
| 140 |
setLogsOpen(false)
|
| 141 |
setLogsEvents([])
|
| 142 |
setLogsError('')
|
| 143 |
+
setLogsPage(1)
|
| 144 |
setLogsStatusLoading(false)
|
| 145 |
setLogsLoading(false)
|
| 146 |
return
|
|
|
|
| 148 |
void carregarLogsStatus()
|
| 149 |
}, [authUser, isAdmin])
|
| 150 |
|
| 151 |
+
useEffect(() => {
|
| 152 |
+
if (!logsEvents.length && logsPage !== 1) {
|
| 153 |
+
setLogsPage(1)
|
| 154 |
+
return
|
| 155 |
+
}
|
| 156 |
+
if (logsPage > logsTotalPages) {
|
| 157 |
+
setLogsPage(logsTotalPages)
|
| 158 |
+
}
|
| 159 |
+
}, [logsEvents.length, logsPage, logsTotalPages])
|
| 160 |
+
|
| 161 |
async function onSubmitLogin(event) {
|
| 162 |
event.preventDefault()
|
| 163 |
setAuthError('')
|
|
|
|
| 211 |
setLogsLoading(true)
|
| 212 |
setLogsError('')
|
| 213 |
try {
|
| 214 |
+
const resp = await api.logsEvents({ scope: logsScope, usuario: logsUsuario, limit: 1000 })
|
| 215 |
setLogsEvents(Array.isArray(resp?.events) ? resp.events : [])
|
| 216 |
+
setLogsPage(1)
|
| 217 |
} catch (err) {
|
| 218 |
setLogsError(err.message || 'Falha ao carregar logs.')
|
| 219 |
setLogsEvents([])
|
| 220 |
+
setLogsPage(1)
|
| 221 |
} finally {
|
| 222 |
setLogsLoading(false)
|
| 223 |
}
|
|
|
|
| 234 |
await carregarEventosLogs()
|
| 235 |
}
|
| 236 |
|
| 237 |
+
function onCloseLogsView() {
|
| 238 |
+
setLogsOpen(false)
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
return (
|
| 242 |
<div className="app-shell">
|
| 243 |
<header className="app-header app-header-logo-only">
|
|
|
|
| 256 |
<button
|
| 257 |
type="button"
|
| 258 |
onClick={() => void onToggleLogs()}
|
| 259 |
+
disabled={logsStatusLoading || (!logsEnabled && !logsOpen)}
|
| 260 |
+
title={logsOpen ? 'Fechar visualização de logs' : !logsEnabled ? logsDisabledReason : 'Abrir leitura de logs'}
|
| 261 |
>
|
| 262 |
+
{logsOpen ? 'Fechar logs' : 'Logs'}
|
| 263 |
</button>
|
| 264 |
) : null}
|
| 265 |
<button type="button" onClick={() => void onLogout()}>
|
|
|
|
| 269 |
</div>
|
| 270 |
) : null}
|
| 271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
{authLoading ? <div className="status-line">Validando autenticação...</div> : null}
|
| 273 |
|
| 274 |
{!authLoading && !authUser ? (
|
|
|
|
| 311 |
) : null}
|
| 312 |
|
| 313 |
{authUser ? (
|
| 314 |
+
logsViewActive ? (
|
| 315 |
+
<section className="logs-panel logs-panel-dedicated">
|
| 316 |
+
<div className="logs-panel-head">
|
| 317 |
+
<div className="logs-panel-head-main">
|
| 318 |
+
<h3>Logs</h3>
|
| 319 |
+
<p className="logs-close-hint">Para voltar ao app, clique em "Fechar logs".</p>
|
| 320 |
+
</div>
|
| 321 |
+
<div className="logs-panel-head-actions">
|
| 322 |
+
<div className="logs-panel-meta">
|
| 323 |
+
<span>{logsStatus?.backend === 'hf_dataset' ? 'Origem: Dataset HF' : 'Origem: indisponível'}</span>
|
| 324 |
+
{logsStatus?.revision ? <span>Revisão: {String(logsStatus.revision).slice(0, 8)}</span> : null}
|
| 325 |
+
</div>
|
| 326 |
+
<button type="button" className="logs-close-btn" onClick={onCloseLogsView}>
|
| 327 |
+
Fechar logs
|
| 328 |
+
</button>
|
| 329 |
+
</div>
|
| 330 |
+
</div>
|
| 331 |
+
|
| 332 |
+
{!logsEnabled ? (
|
| 333 |
+
<div className="section1-empty-hint">{logsDisabledReason}</div>
|
| 334 |
+
) : (
|
| 335 |
+
<>
|
| 336 |
+
<div className="logs-filters">
|
| 337 |
+
<div className="logs-field">
|
| 338 |
+
<label htmlFor="logs-scope">Escopo</label>
|
| 339 |
+
<select id="logs-scope" value={logsScope} onChange={(event) => setLogsScope(event.target.value)}>
|
| 340 |
+
<option value="">Todos</option>
|
| 341 |
+
<option value="auth">Auth</option>
|
| 342 |
+
<option value="repositorio">Repositório</option>
|
| 343 |
+
<option value="elaboracao">Elaboração</option>
|
| 344 |
+
<option value="visualizacao">Visualização</option>
|
| 345 |
+
</select>
|
| 346 |
+
</div>
|
| 347 |
+
<div className="logs-field">
|
| 348 |
+
<label htmlFor="logs-usuario">Usuário</label>
|
| 349 |
+
<input
|
| 350 |
+
id="logs-usuario"
|
| 351 |
+
type="text"
|
| 352 |
+
value={logsUsuario}
|
| 353 |
+
onChange={(event) => setLogsUsuario(event.target.value)}
|
| 354 |
+
placeholder="filtrar por usuário"
|
| 355 |
+
autoComplete="off"
|
| 356 |
+
/>
|
| 357 |
+
</div>
|
| 358 |
+
<button type="button" onClick={() => void carregarEventosLogs()} disabled={logsLoading}>
|
| 359 |
+
Atualizar
|
| 360 |
+
</button>
|
| 361 |
+
</div>
|
| 362 |
+
|
| 363 |
+
{logsError ? <div className="error-line">{logsError}</div> : null}
|
| 364 |
+
|
| 365 |
+
<div className="table-container">
|
| 366 |
+
<table className="logs-table">
|
| 367 |
+
<thead>
|
| 368 |
+
<tr>
|
| 369 |
+
<th>Timestamp</th>
|
| 370 |
+
<th>Usuário</th>
|
| 371 |
+
<th>Perfil</th>
|
| 372 |
+
<th>Escopo</th>
|
| 373 |
+
<th>Ação</th>
|
| 374 |
+
<th>Status</th>
|
| 375 |
+
<th>Detalhes</th>
|
| 376 |
+
</tr>
|
| 377 |
+
</thead>
|
| 378 |
+
<tbody>
|
| 379 |
+
{logsVisibleEvents.map((item) => (
|
| 380 |
+
<tr key={item.event_id || `${item.ts}-${item.action}`}>
|
| 381 |
+
<td>{item.ts || '-'}</td>
|
| 382 |
+
<td>{item.usuario || '-'}</td>
|
| 383 |
+
<td>{item.perfil || '-'}</td>
|
| 384 |
+
<td>{item.scope || '-'}</td>
|
| 385 |
+
<td>{item.action || '-'}</td>
|
| 386 |
+
<td>{item.status || '-'}</td>
|
| 387 |
+
<td className="logs-table-details">{JSON.stringify(item.details || {})}</td>
|
| 388 |
+
</tr>
|
| 389 |
+
))}
|
| 390 |
+
{!logsEvents.length ? (
|
| 391 |
+
<tr>
|
| 392 |
+
<td colSpan={7}>{logsLoading ? 'Carregando logs...' : 'Nenhum log encontrado para os filtros.'}</td>
|
| 393 |
+
</tr>
|
| 394 |
+
) : null}
|
| 395 |
+
</tbody>
|
| 396 |
+
</table>
|
| 397 |
+
</div>
|
| 398 |
+
|
| 399 |
+
<div className="logs-pagination">
|
| 400 |
+
<span>{logsEvents.length ? `Exibindo ${logsStartIndex + 1}-${logsEndIndex} de ${logsEvents.length}` : 'Exibindo 0 de 0'}</span>
|
| 401 |
+
<div className="logs-pagination-actions">
|
| 402 |
+
<button
|
| 403 |
+
type="button"
|
| 404 |
+
onClick={() => setLogsPage((prev) => Math.max(1, prev - 1))}
|
| 405 |
+
disabled={logsLoading || logsCurrentPage <= 1}
|
| 406 |
+
>
|
| 407 |
+
Anterior
|
| 408 |
+
</button>
|
| 409 |
+
<span>Página {logsCurrentPage} de {logsTotalPages}</span>
|
| 410 |
+
<button
|
| 411 |
+
type="button"
|
| 412 |
+
onClick={() => setLogsPage((prev) => Math.min(logsTotalPages, prev + 1))}
|
| 413 |
+
disabled={logsLoading || logsCurrentPage >= logsTotalPages}
|
| 414 |
+
>
|
| 415 |
+
Próxima
|
| 416 |
+
</button>
|
| 417 |
+
</div>
|
| 418 |
+
</div>
|
| 419 |
+
</>
|
| 420 |
+
)}
|
| 421 |
+
</section>
|
| 422 |
+
) : (
|
| 423 |
+
<>
|
| 424 |
<nav className="tabs" aria-label="Navegação principal">
|
| 425 |
{TABS.map((tab) => {
|
| 426 |
const active = tab.key === activeTab
|
|
|
|
| 459 |
<div className="tab-pane" hidden={activeTab !== 'Repositório'}>
|
| 460 |
<RepositorioTab authUser={authUser} />
|
| 461 |
</div>
|
| 462 |
+
</>
|
| 463 |
+
)
|
| 464 |
) : null}
|
| 465 |
</div>
|
| 466 |
)
|
frontend/src/styles.css
CHANGED
|
@@ -328,21 +328,43 @@ textarea {
|
|
| 328 |
padding: 12px;
|
| 329 |
}
|
| 330 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
.logs-panel-head {
|
| 332 |
display: flex;
|
| 333 |
justify-content: space-between;
|
| 334 |
-
align-items:
|
| 335 |
gap: 10px;
|
| 336 |
flex-wrap: wrap;
|
| 337 |
margin-bottom: 10px;
|
| 338 |
}
|
| 339 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
.logs-panel-head h3 {
|
| 341 |
margin: 0;
|
| 342 |
font-family: 'Sora', sans-serif;
|
| 343 |
color: #2b4258;
|
| 344 |
}
|
| 345 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 346 |
.logs-panel-meta {
|
| 347 |
display: inline-flex;
|
| 348 |
gap: 10px;
|
|
@@ -350,9 +372,16 @@ textarea {
|
|
| 350 |
font-size: 0.82rem;
|
| 351 |
}
|
| 352 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
.logs-filters {
|
| 354 |
display: grid;
|
| 355 |
-
grid-template-columns: 1.1fr 1.2fr
|
| 356 |
gap: 10px;
|
| 357 |
align-items: end;
|
| 358 |
margin-bottom: 10px;
|
|
@@ -398,6 +427,27 @@ textarea {
|
|
| 398 |
max-width: 420px;
|
| 399 |
}
|
| 400 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
.inner-tabs {
|
| 402 |
display: flex;
|
| 403 |
flex-wrap: wrap;
|
|
|
|
| 328 |
padding: 12px;
|
| 329 |
}
|
| 330 |
|
| 331 |
+
.logs-panel-dedicated {
|
| 332 |
+
margin-top: 10px;
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
.logs-panel-head {
|
| 336 |
display: flex;
|
| 337 |
justify-content: space-between;
|
| 338 |
+
align-items: flex-start;
|
| 339 |
gap: 10px;
|
| 340 |
flex-wrap: wrap;
|
| 341 |
margin-bottom: 10px;
|
| 342 |
}
|
| 343 |
|
| 344 |
+
.logs-panel-head-main {
|
| 345 |
+
display: grid;
|
| 346 |
+
gap: 4px;
|
| 347 |
+
}
|
| 348 |
+
|
| 349 |
.logs-panel-head h3 {
|
| 350 |
margin: 0;
|
| 351 |
font-family: 'Sora', sans-serif;
|
| 352 |
color: #2b4258;
|
| 353 |
}
|
| 354 |
|
| 355 |
+
.logs-close-hint {
|
| 356 |
+
margin: 0;
|
| 357 |
+
color: #5f7388;
|
| 358 |
+
font-size: 0.82rem;
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
.logs-panel-head-actions {
|
| 362 |
+
display: inline-flex;
|
| 363 |
+
align-items: center;
|
| 364 |
+
gap: 10px;
|
| 365 |
+
flex-wrap: wrap;
|
| 366 |
+
}
|
| 367 |
+
|
| 368 |
.logs-panel-meta {
|
| 369 |
display: inline-flex;
|
| 370 |
gap: 10px;
|
|
|
|
| 372 |
font-size: 0.82rem;
|
| 373 |
}
|
| 374 |
|
| 375 |
+
.logs-close-btn {
|
| 376 |
+
border: 1px solid #adc3d8;
|
| 377 |
+
background: #eef5fb;
|
| 378 |
+
color: #2f4760;
|
| 379 |
+
font-weight: 700;
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
.logs-filters {
|
| 383 |
display: grid;
|
| 384 |
+
grid-template-columns: 1.1fr 1.2fr auto;
|
| 385 |
gap: 10px;
|
| 386 |
align-items: end;
|
| 387 |
margin-bottom: 10px;
|
|
|
|
| 427 |
max-width: 420px;
|
| 428 |
}
|
| 429 |
|
| 430 |
+
.logs-pagination {
|
| 431 |
+
margin-top: 10px;
|
| 432 |
+
display: flex;
|
| 433 |
+
align-items: center;
|
| 434 |
+
justify-content: space-between;
|
| 435 |
+
gap: 10px;
|
| 436 |
+
flex-wrap: wrap;
|
| 437 |
+
color: #4b6278;
|
| 438 |
+
font-size: 0.82rem;
|
| 439 |
+
}
|
| 440 |
+
|
| 441 |
+
.logs-pagination-actions {
|
| 442 |
+
display: inline-flex;
|
| 443 |
+
align-items: center;
|
| 444 |
+
gap: 8px;
|
| 445 |
+
}
|
| 446 |
+
|
| 447 |
+
.logs-pagination-actions button {
|
| 448 |
+
min-width: 96px;
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
.inner-tabs {
|
| 452 |
display: flex;
|
| 453 |
flex-wrap: wrap;
|