Admin sekce.

master
Kowalski 2026-09-06 12:47:28 +02:00
parent 7a98fb3f53
commit 3688c0312d
9 changed files with 238 additions and 4 deletions

View File

@ -0,0 +1,27 @@
<?php
namespace App\Model\Facade;
use Nette\Database\Connection;
class DatabazeFacade
{
public function __construct(
private Connection $masterDb
) {
}
/**
* Seznam databází z master tabulky LOGIN (jeden login = jedna databáze).
*
* @return \Nette\Database\Row[]
*/
public function getAll(): array
{
return $this->masterDb->fetchAll(
'SELECT ID, EMAIL, POSLEDNI_PRIHLASENI, AKTIVNI, [DATABASE], [SERVER], FIRMA_ID, IS_ADMIN
FROM dbo.LOGIN
ORDER BY [DATABASE], EMAIL'
);
}
}

View File

@ -28,7 +28,7 @@ class Authenticator implements IAuthenticator
// 1. Načtení uživatele podle tvého přesného schématu UZIVATEL
$row = $this->masterDb->fetch(
'SELECT ID, EMAIL, HESLO_HASH, [DATABASE], [SERVER], FIRMA_ID
'SELECT ID, EMAIL, HESLO_HASH, [DATABASE], [SERVER], FIRMA_ID, IS_ADMIN
FROM dbo.LOGIN
WHERE EMAIL = ? AND AKTIVNI = 1',
$email
@ -57,14 +57,17 @@ class Authenticator implements IAuthenticator
//$firmaId = $firmaRow ? $firmaRow->FIRMA_ID : $row->ID;
// 4. Předání dat do Nette Identity
$isAdmin = (bool) $row->IS_ADMIN;
return new SimpleIdentity(
$row->ID,
null, // role
$isAdmin ? 'admin' : 'user',
[
'email' => $row->EMAIL,
'database' => $row->DATABASE, // Nulllable -> KufrikConnectionFactory použije default_db
'server' => $row->SERVER,
'firma_id' => (int) $row->FIRMA_ID, // Zpřístupní $user->identity->firma_id
'is_admin' => $isAdmin,
]
);
}

View File

@ -65,6 +65,16 @@
<i class="bi bi-box me-1"></i> Kufřík
</button>
</li>
<!-- ADMIN — pouze systémový administrátor -->
{if $user->isInRole('admin')}
<li class="nav-item" role="presentation">
<button n:class="nav-link, $presenter->isLinkCurrent('Admin:*') ? active"
id="cat-admin-tab" data-bs-toggle="tab" data-bs-target="#toolbar-admin" type="button" role="tab">
<i class="bi bi-shield-lock me-1"></i> Admin
</button>
</li>
{/if}
</ul>
{/if}
@ -214,6 +224,22 @@
</div>
</div>
<!-- ADMIN -->
{if $user->isInRole('admin')}
<div n:class="tab-pane, fade, $presenter->isLinkCurrent('Admin:*') ? 'show active'" id="toolbar-admin" role="tabpanel">
<div class="action-buttons">
<a n:href="Admin:databaze" n:class="btn, btn-action, $presenter->isLinkCurrent('Admin:databaze') ? 'btn-primary active' : 'btn-outline-primary'">
<i class="bi bi-database-fill"></i>
<span>Databáze</span>
</a>
<a n:href="Admin:statistika" n:class="btn, btn-action, $presenter->isLinkCurrent('Admin:statistika') ? 'btn-primary active' : 'btn-outline-primary'">
<i class="bi bi-graph-up"></i>
<span>Statistika</span>
</a>
</div>
</div>
{/if}
</div>
</div>

View File

@ -0,0 +1,49 @@
<?php
namespace App\UI\Admin;
use App\Model\Facade\DatabazeFacade;
use App\UI\BasePresenter;
class AdminPresenter extends BasePresenter
{
public function __construct(
private DatabazeFacade $databazeFacade
) {
parent::__construct();
}
protected function startup(): void
{
parent::startup();
if (!$this->getUser()->isInRole('admin')) {
$this->error('Sekce Admin je dostupná pouze administrátorovi systému.', 403);
}
}
public function actionDefault(): void
{
$this->redirect('databaze');
}
public function renderDatabaze(): void
{
$loginy = $this->databazeFacade->getAll();
$aktivniCount = 0;
foreach ($loginy as $login) {
if ($login->AKTIVNI) {
$aktivniCount++;
}
}
$this->template->loginy = $loginy;
$this->template->celkemCount = count($loginy);
$this->template->aktivniCount = $aktivniCount;
}
public function renderStatistika(): void
{
}
}

View File

@ -0,0 +1,95 @@
{block content}
<!-- DataTables CSS s Bootstrap 5 vzhledem -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/css/dataTables.bootstrap5.min.css">
<div class="container-fluid py-3">
<div class="d-flex justify-content-between align-items-center mb-3">
<h2 class="h3 mb-0">Přehled databází</h2>
</div>
<div class="row g-3 mb-4">
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm p-3 bg-white border-start border-primary border-4">
<div class="text-muted small">Celkem databází</div>
<div class="fs-4 fw-bold text-primary">{$celkemCount} ks</div>
</div>
</div>
<div class="col-md-6 col-lg-4">
<div class="card border-0 shadow-sm p-3 bg-white border-start border-success border-4">
<div class="text-muted small">Aktivní</div>
<div class="fs-4 fw-bold text-success">{$aktivniCount} ks</div>
</div>
</div>
</div>
<div class="card shadow-sm border-0 p-3 bg-white">
<div class="table-responsive">
<table class="table table-hover align-middle w-100" id="databaze-table">
<thead class="table-light">
<tr>
<th>Databáze</th>
<th>Server</th>
<th>Login</th>
<th class="text-center">Firma ID</th>
<th>Poslední přihlášení</th>
<th class="text-center">Stav</th>
<th class="text-center">Role</th>
</tr>
</thead>
<tbody>
{foreach $loginy as $login}
<tr n:class="!$login->AKTIVNI ? 'table-light text-muted'">
<td class="fw-bold" data-order="{$login->DATABASE}">
<i class="bi bi-database me-1 text-muted"></i>{$login->DATABASE}
</td>
<td>{$login->SERVER}</td>
<td>
<a href="mailto:{$login->EMAIL}" class="text-decoration-none">{$login->EMAIL}</a>
</td>
<td class="text-center">{$login->FIRMA_ID}</td>
<td data-order="{if $login->POSLEDNI_PRIHLASENI}{$login->POSLEDNI_PRIHLASENI|date:'YmdHis'}{/if}">
{if $login->POSLEDNI_PRIHLASENI}
{$login->POSLEDNI_PRIHLASENI|date:'d.m.Y H:i'}
{else}
<span class="text-muted">—</span>
{/if}
</td>
<td class="text-center" data-order="{$login->AKTIVNI}">
{if $login->AKTIVNI}
<span class="badge bg-success">Aktivní</span>
{else}
<span class="badge bg-secondary">Neaktivní</span>
{/if}
</td>
<td class="text-center" data-order="{$login->IS_ADMIN}">
{if $login->IS_ADMIN}
<span class="badge bg-dark">Admin</span>
{else}
<span class="badge bg-light text-dark border">Uživatel</span>
{/if}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/datatables.net@1.13.6/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/datatables.net-bs5@1.13.6/js/dataTables.bootstrap5.min.js"></script>
{syntax off}
<script>
$(document).ready(function() {
$('#databaze-table').DataTable({
language: {
url: 'https://cdn.datatables.net/plug-ins/1.13.6/i18n/cs.json'
},
order: [[0, 'asc']],
pageLength: 25
});
});
</script>
{/syntax}

View File

@ -0,0 +1,11 @@
{block content}
<div class="container-fluid">
<div class="content-card my-3">
<h2 class="h4 mb-3" style="color: var(--header-bg);">
<i class="bi bi-graph-up me-2"></i>Statistika
</h2>
<hr style="border-color: var(--border-color); margin-bottom: 1.5rem;">
<p class="text-muted mb-0">Systémové statistiky budou doplněny.</p>
</div>
</div>

View File

@ -6,6 +6,9 @@ services:
# Authenticator si vyžádá připojení k Master DB
- App\Model\Security\Authenticator(@database.default.connection)
# Seznam databází (LOGIN) z Master DB
- App\Model\Facade\DatabazeFacade(@database.default.connection)
# 1. Factory pro klientskou databázi
kufrikConnectionFactory:
factory: App\Model\Database\KufrikConnectionFactory(%clientDb.user%, %clientDb.password%)

View File

@ -44,6 +44,7 @@ BEGIN
[DATABASE] [nvarchar](128) NOT NULL,
[SERVER] [nvarchar](100) NOT NULL,
[FIRMA_ID] [int] NOT NULL, -- vazna na tabulku FIRMA v datové DB
[IS_ADMIN] [bit] NOT NULL CONSTRAINT [DF_LOGIN_IS_ADMIN] DEFAULT 0,
CONSTRAINT [PK_LOGIN] PRIMARY KEY CLUSTERED ([ID] ASC),
CONSTRAINT [UQ_LOGIN_EMAIL] UNIQUE NONCLUSTERED ([EMAIL] ASC)
@ -58,7 +59,8 @@ INSERT INTO dbo.[LOGIN] (
[HESLO_HASH],
[DATABASE],
[SERVER],
[FIRMA_ID]
[FIRMA_ID],
[IS_ADMIN]
)
VALUES (
1,
@ -67,11 +69,20 @@ VALUES (
N'$2y$12$9SAWctZKp/JpYl7K.awSRehXOXVyRpu0ofReqvbm1jVjvdz41zH7a',
N'kufrik',
N'localhost',
1 -- Odkaz na FIRMA.ID = 1 v klientské databázi 'kufrik'
1, -- Odkaz na FIRMA.ID = 1 v klientské databázi 'kufrik'
1 -- systémový administrátor
);
SET IDENTITY_INSERT dbo.[LOGIN] OFF;
GO
-- Existující instalace: sloupec IS_ADMIN
IF COL_LENGTH('dbo.LOGIN', 'IS_ADMIN') IS NULL
BEGIN
ALTER TABLE dbo.LOGIN ADD [IS_ADMIN] [bit] NOT NULL CONSTRAINT [DF_LOGIN_IS_ADMIN] DEFAULT 0;
UPDATE dbo.LOGIN SET [IS_ADMIN] = 1 WHERE [EMAIL] = N'admin@kmetix.cz';
END
GO
-- ==========================================
-- DATOVÁ databáze
-- ==========================================

View File

@ -0,0 +1,9 @@
USE [kufrik_master]
GO
IF COL_LENGTH('dbo.LOGIN', 'IS_ADMIN') IS NULL
BEGIN
ALTER TABLE dbo.LOGIN ADD [IS_ADMIN] [bit] NOT NULL CONSTRAINT [DF_LOGIN_IS_ADMIN] DEFAULT 0;
UPDATE dbo.LOGIN SET [IS_ADMIN] = 1 WHERE [EMAIL] = N'admin@kmetix.cz';
END
GO