Files
Vasyka eaa05d68c1 Deploy 2: 2FA (App + Email) + REST API + CSV import-export + auto backup
- Filament v5 multiFactorAuthentication enabled on both panels (App + Email)
- HasAppAuthentication + HasEmailAuthentication on User and SuperAdmin
- Migration: app_authentication_secret + recovery_codes + email_authentication_at
- Sanctum REST API: /api/v1/login, /me, clients, vehicles, work-orders
- EnsureTokenMatchesTenant middleware blocks cross-tenant token usage
- CsvImportExport service: clients + vehicles bulk via plain CSV
- Import/Export buttons on Client + Vehicle list pages
- ApiTokens page in tenant panel (generate/revoke + last-used)
- BackupAllTenantsCommand + scheduler (daily 03:00, retain 14 days)
- Background scheduler in entrypoint.sh
2026-05-07 19:25:27 +00:00

88 lines
4.8 KiB
PHP

<x-filament-panels::page>
<style>
.at-card { background: #fff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 16px; }
.dark .at-card { background: #1f2937; border-color: #374151; }
.at-token {
background: #1f2937; color: #fbbf24;
font-family: ui-monospace, monospace; font-size: 12px;
padding: 12px; border-radius: 6px; word-break: break-all;
margin: 12px 0;
}
.at-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.at-table th, .at-table td { text-align: left; padding: 8px 12px; border-bottom: 1px solid #f3f4f6; }
.dark .at-table th, .dark .at-table td { border-color: #374151; }
.at-table th { font-weight: 600; color: #6b7280; font-size: 11px; text-transform: uppercase; }
.at-empty { padding: 32px; text-align: center; color: #9ca3af; font-size: 13px; }
.at-revoke { background: #ef4444; color: white; padding: 4px 8px; border-radius: 4px; font-size: 11px; cursor: pointer; border: none; }
.at-revoke:hover { background: #dc2626; }
.at-doc { background: #eff6ff; border-left: 3px solid #3b82f6; padding: 12px 16px; border-radius: 4px; font-size: 12px; line-height: 1.6; }
.dark .at-doc { background: #1e3a8a40; }
.at-doc code { background: rgba(0,0,0,.06); padding: 2px 6px; border-radius: 3px; font-family: ui-monospace, monospace; font-size: 11px; }
.dark .at-doc code { background: rgba(255,255,255,.1); }
</style>
@if ($newToken)
<div class="at-card" style="border-color: #f59e0b; border-width: 2px;">
<h3 style="font-weight:600;font-size:15px;margin-bottom:6px;"> Token-ul tău nou (afișat o singură dată):</h3>
<div class="at-token" x-data="{ copied: false }">
<span x-ref="t">{{ $newToken }}</span>
<button type="button"
@click="navigator.clipboard.writeText($refs.t.innerText); copied = true; setTimeout(() => copied = false, 2000)"
style="margin-left:12px;background:#3b82f6;color:white;padding:4px 10px;border:none;border-radius:4px;cursor:pointer;font-size:11px;">
<span x-show="!copied">📋 Copiază</span>
<span x-show="copied" x-cloak> Copiat</span>
</button>
</div>
<button type="button" wire:click="dismissNew" style="font-size:12px;color:#6b7280;background:none;border:none;cursor:pointer;">
Am salvat token-ul, ascunde-l
</button>
</div>
@endif
<div class="at-card" style="margin-top:16px;">
@php $tokens = $this->getTokens(); @endphp
@if (empty($tokens))
<div class="at-empty">Niciun token generat. Folosește butonul „Generează token" de sus.</div>
@else
<table class="at-table">
<thead>
<tr>
<th>Nume</th>
<th>Folosit ultima dată</th>
<th>Creat</th>
<th>Permisiuni</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach ($tokens as $t)
<tr>
<td><b>{{ $t['name'] }}</b></td>
<td>{{ $t['last_used_at'] }}</td>
<td>{{ $t['created_at'] }}</td>
<td><code style="font-size:11px;">{{ $t['abilities'] }}</code></td>
<td>
<button type="button"
wire:click="deleteToken({{ $t['id'] }})"
wire:confirm="Revoci token-ul {{ $t['name'] }}"? Apelurile API vor eșua imediat."
class="at-revoke">Revocă</button>
</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
<div class="at-doc" style="margin-top:16px;">
<b>Cum folosești API-ul:</b>
<ol style="margin:8px 0 0 20px;">
<li>Obține token cu <code>POST /api/v1/login</code> cu <code>email</code>+<code>password</code> JSON</li>
<li>Sau generează unul aici (recomandat pentru integrări permanente)</li>
<li>Adaugă header <code>Authorization: Bearer &lt;token&gt;</code> la fiecare request</li>
<li>Endpoints disponibile: <code>/api/v1/clients</code>, <code>/api/v1/vehicles</code>, <code>/api/v1/work-orders</code> (GET, POST, PATCH, DELETE)</li>
<li>Tot apelul trebuie făcut pe subdomain-ul tău: <code>https://{{ tenant()?->slug ?? 'YOURSLUG' }}.service.mir.md/api/v1/...</code></li>
</ol>
</div>
</x-filament-panels::page>