atualizacao
This commit is contained in:
149
app/Console/Commands/BackfillConditionalAccessDerivedData.php
Normal file
149
app/Console/Commands/BackfillConditionalAccessDerivedData.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BackfillConditionalAccessDerivedData extends Command
|
||||
{
|
||||
protected $signature = 'conditional-access:backfill-derived {--chunk=1000} {--only-empty} {--no-policies}';
|
||||
protected $description = 'Preenche colunas derivadas e sincroniza políticas da tabela acesso_condicionals';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$chunkSize = max((int) $this->option('chunk'), 100);
|
||||
$onlyEmpty = (bool) $this->option('only-empty');
|
||||
$syncPolicies = ! (bool) $this->option('no-policies');
|
||||
|
||||
$query = DB::table('acesso_condicionals')
|
||||
->select([
|
||||
'id',
|
||||
'location_json',
|
||||
'device_detail_json',
|
||||
'status_json',
|
||||
'applied_policies_json',
|
||||
'location_key',
|
||||
'device_operating_system',
|
||||
'status_error_code',
|
||||
])
|
||||
->orderBy('id');
|
||||
|
||||
if ($onlyEmpty) {
|
||||
$query->where(function ($subQuery) {
|
||||
$subQuery->whereNull('location_key')
|
||||
->orWhereNull('device_operating_system')
|
||||
->orWhereNull('status_error_code');
|
||||
});
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar((clone $query)->count());
|
||||
$bar->start();
|
||||
|
||||
$query->chunkById($chunkSize, function ($rows) use ($syncPolicies, $bar) {
|
||||
$policyRowsToInsert = [];
|
||||
$ids = [];
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$ids[] = $row->id;
|
||||
|
||||
$location = $this->decodeJson($row->location_json ?? null);
|
||||
$device = $this->decodeJson($row->device_detail_json ?? null);
|
||||
$status = $this->decodeJson($row->status_json ?? null);
|
||||
$policies = $this->decodeJson($row->applied_policies_json ?? null);
|
||||
|
||||
$city = trim((string) ($location['city'] ?? ''));
|
||||
$state = trim((string) ($location['state'] ?? ''));
|
||||
$country = trim((string) ($location['countryOrRegion'] ?? ''));
|
||||
$locationKey = mb_strtolower($city . '|' . $state . '|' . $country);
|
||||
$locationKey = $locationKey === '||' ? null : $locationKey;
|
||||
|
||||
DB::table('acesso_condicionals')
|
||||
->where('id', $row->id)
|
||||
->update([
|
||||
'location_city' => $city !== '' ? $city : null,
|
||||
'location_state' => $state !== '' ? $state : null,
|
||||
'location_country' => $country !== '' ? $country : null,
|
||||
'location_key' => $locationKey,
|
||||
'device_display_name' => $this->nullableString($device['displayName'] ?? $device['deviceId'] ?? null),
|
||||
'device_operating_system' => $this->nullableString($device['operatingSystem'] ?? null),
|
||||
'device_browser' => $this->nullableString($device['browser'] ?? null),
|
||||
'device_is_compliant' => array_key_exists('isCompliant', (array) $device) ? (int) ((bool) $device['isCompliant']) : null,
|
||||
'device_is_managed' => array_key_exists('isManaged', (array) $device) ? (int) ((bool) $device['isManaged']) : null,
|
||||
'status_error_code' => $this->nullableString(isset($status['errorCode']) ? (string) $status['errorCode'] : null),
|
||||
'status_failure_reason' => $this->nullableString($status['failureReason'] ?? null),
|
||||
]);
|
||||
|
||||
if ($syncPolicies && is_array($policies)) {
|
||||
foreach ($policies as $policy) {
|
||||
if (! is_array($policy)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$policyRowsToInsert[] = [
|
||||
'acesso_conditional_id' => $row->id,
|
||||
'policy_id' => $this->nullableString($policy['id'] ?? null),
|
||||
'policy_name' => trim((string) ($policy['displayName'] ?? 'Sem nome')) ?: 'Sem nome',
|
||||
'policy_result' => $this->nullableString($policy['result'] ?? null),
|
||||
'grant_controls_json' => $this->jsonOrNull($policy['enforcedGrantControls'] ?? null),
|
||||
'session_controls_json' => $this->jsonOrNull($policy['enforcedSessionControls'] ?? null),
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
if ($syncPolicies && $ids !== []) {
|
||||
DB::table('acesso_conditional_policies')->whereIn('acesso_conditional_id', $ids)->delete();
|
||||
|
||||
foreach (array_chunk($policyRowsToInsert, 1000) as $insertChunk) {
|
||||
if ($insertChunk !== []) {
|
||||
DB::table('acesso_conditional_policies')->insert($insertChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'id');
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine(2);
|
||||
$this->info('Backfill concluído com sucesso.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function decodeJson(mixed $value): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (! is_string($value) || trim($value) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
return json_last_error() === JSON_ERROR_NONE ? $decoded : null;
|
||||
}
|
||||
|
||||
private function nullableString(mixed $value): ?string
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim((string) $value);
|
||||
return $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
private function jsonOrNull(mixed $value): ?string
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
}
|
||||
453
app/Console/Commands/ProjectConditionalAccessShadowTables.php
Normal file
453
app/Console/Commands/ProjectConditionalAccessShadowTables.php
Normal file
@@ -0,0 +1,453 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class ProjectConditionalAccessShadowTables extends Command
|
||||
{
|
||||
protected $signature = 'conditional-access:project-shadow
|
||||
{--chunk=1000 : Quantidade de registros por lote}
|
||||
{--from-id= : Processa apenas a partir deste id de origem}
|
||||
{--only-missing : Processa apenas eventos ainda não projetados}
|
||||
{--rebuild-policies : Recria as políticas dos eventos processados}';
|
||||
|
||||
protected $description = 'Projeta a tabela fonte acesso_condicionals em tabelas shadow indexadas, sem alterar a tabela original, com cliente_id e tenant_id.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
if (! Schema::hasTable('acesso_condicionals')) {
|
||||
$this->error('Tabela fonte acesso_condicionals não encontrada.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('m365_creds')) {
|
||||
$this->error('Tabela m365_creds não encontrada. Ela é necessária para resolver cliente_id a partir do tenant_id.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'tenant_id')) {
|
||||
$this->error('A coluna tenant_id não foi encontrada em acesso_condicionals.');
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$hasClientesTable = Schema::hasTable('clientes');
|
||||
$chunk = max((int) $this->option('chunk'), 100);
|
||||
$fromId = $this->option('from-id') !== null ? max((int) $this->option('from-id'), 1) : null;
|
||||
$onlyMissing = (bool) $this->option('only-missing');
|
||||
$rebuildPolicies = (bool) $this->option('rebuild-policies');
|
||||
|
||||
$query = DB::table('acesso_condicionals')
|
||||
->select([
|
||||
'id',
|
||||
'tenant_id',
|
||||
'created_date_time',
|
||||
'user_display_name',
|
||||
'user_principal_name',
|
||||
'app_display_name',
|
||||
'ip_address',
|
||||
'conditional_access_status',
|
||||
'location_json',
|
||||
'device_detail_json',
|
||||
'status_json',
|
||||
'applied_policies_json',
|
||||
])
|
||||
->orderBy('id');
|
||||
|
||||
if ($fromId !== null) {
|
||||
$query->where('id', '>=', $fromId);
|
||||
}
|
||||
|
||||
if ($onlyMissing) {
|
||||
$query->whereNotExists(function ($subQuery) {
|
||||
$subQuery->selectRaw('1')
|
||||
->from('acesso_conditional_event_index as i')
|
||||
->whereColumn('i.source_event_id', 'acesso_condicionals.id');
|
||||
});
|
||||
}
|
||||
|
||||
$total = (clone $query)->count('id');
|
||||
$this->info('Eventos elegíveis para projeção: ' . number_format($total, 0, ',', '.'));
|
||||
|
||||
if ($total === 0) {
|
||||
$this->info('Nada a processar.');
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
$progress = $this->output->createProgressBar($total);
|
||||
$progress->start();
|
||||
|
||||
$query->chunkById($chunk, function ($rows) use ($progress, $rebuildPolicies, $hasClientesTable) {
|
||||
$tenantIds = $rows->pluck('tenant_id')
|
||||
->map(fn ($value) => $this->nullableString($value))
|
||||
->filter()
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$credsByTenant = [];
|
||||
if ($tenantIds !== []) {
|
||||
$credsByTenant = DB::table('m365_creds')
|
||||
->whereIn('id_tenant', $tenantIds)
|
||||
->orderBy('id')
|
||||
->get(['id', 'id_tenant', 'id_cliente'])
|
||||
->groupBy('id_tenant')
|
||||
->map(fn ($group) => $group->first())
|
||||
->all();
|
||||
}
|
||||
|
||||
$clienteIds = [];
|
||||
foreach ($credsByTenant as $cred) {
|
||||
$clienteId = $this->nullableUnsignedBigInt($cred->id_cliente ?? null);
|
||||
if ($clienteId !== null) {
|
||||
$clienteIds[] = $clienteId;
|
||||
}
|
||||
}
|
||||
$clienteIds = array_values(array_unique($clienteIds));
|
||||
|
||||
$clientesById = [];
|
||||
if ($hasClientesTable && $clienteIds !== []) {
|
||||
$clientesById = DB::table('clientes')
|
||||
->whereIn('id', $clienteIds)
|
||||
->pluck('name', 'id')
|
||||
->all();
|
||||
}
|
||||
|
||||
$eventRows = [];
|
||||
$policiesPerEvent = [];
|
||||
$sourceIds = [];
|
||||
$now = now();
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$sourceIds[] = (int) $row->id;
|
||||
|
||||
$tenantId = $this->nullableString($row->tenant_id ?? null);
|
||||
$cred = $tenantId !== null ? ($credsByTenant[$tenantId] ?? null) : null;
|
||||
$m365CredId = $cred?->id !== null ? (int) $cred->id : null;
|
||||
$clienteId = $this->nullableUnsignedBigInt($cred->id_cliente ?? null);
|
||||
$clienteName = $clienteId !== null ? $this->nullableString($clientesById[$clienteId] ?? null) : null;
|
||||
|
||||
$location = $this->decodeJson($row->location_json ?? null);
|
||||
$device = $this->decodeJson($row->device_detail_json ?? null);
|
||||
$status = $this->decodeJson($row->status_json ?? null);
|
||||
$policies = $this->decodeJson($row->applied_policies_json ?? null);
|
||||
$policyList = is_array($policies) ? array_values(array_filter($policies, 'is_array')) : [];
|
||||
|
||||
$city = $this->nullableString($location['city'] ?? null);
|
||||
$state = $this->nullableString($location['state'] ?? null);
|
||||
$country = $this->nullableString($location['countryOrRegion'] ?? null);
|
||||
$locationKey = $this->buildLocationKey($city, $state, $country);
|
||||
|
||||
[$executiveOutcome, $policyCount] = $this->deriveEventOutcome($policyList);
|
||||
|
||||
$eventRows[] = [
|
||||
'source_event_id' => (int) $row->id,
|
||||
'cliente_id' => $clienteId,
|
||||
'cliente_name' => $clienteName,
|
||||
'm365_cred_id' => $m365CredId,
|
||||
'tenant_id' => $tenantId,
|
||||
'source_payload_hash' => hash('sha256', json_encode([
|
||||
'tenant_id' => $tenantId,
|
||||
'created_date_time' => $row->created_date_time,
|
||||
'user_display_name' => $row->user_display_name,
|
||||
'user_principal_name' => $row->user_principal_name,
|
||||
'app_display_name' => $row->app_display_name,
|
||||
'ip_address' => $row->ip_address,
|
||||
'conditional_access_status' => $row->conditional_access_status,
|
||||
'location_json' => $row->location_json,
|
||||
'device_detail_json' => $row->device_detail_json,
|
||||
'status_json' => $row->status_json,
|
||||
'applied_policies_json' => $row->applied_policies_json,
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)),
|
||||
'created_date_time' => $row->created_date_time,
|
||||
'user_display_name' => $this->nullableString($row->user_display_name),
|
||||
'user_principal_name' => $this->nullableString($row->user_principal_name),
|
||||
'app_display_name' => $this->nullableString($row->app_display_name),
|
||||
'ip_address' => $this->nullableString($row->ip_address),
|
||||
'conditional_access_status' => $this->nullableString($row->conditional_access_status),
|
||||
'conditional_access_status_class' => $this->classifyAccessStatus((string) ($row->conditional_access_status ?? '')),
|
||||
'executive_outcome_class' => $executiveOutcome,
|
||||
'location_city' => $city,
|
||||
'location_state' => $state,
|
||||
'location_country' => $country,
|
||||
'location_key' => $locationKey,
|
||||
'device_display_name' => $this->nullableString($device['displayName'] ?? $device['deviceId'] ?? null),
|
||||
'device_operating_system' => $this->nullableString($device['operatingSystem'] ?? null),
|
||||
'device_browser' => $this->nullableString($device['browser'] ?? null),
|
||||
'device_is_compliant' => array_key_exists('isCompliant', (array) $device) ? (int) ((bool) $device['isCompliant']) : null,
|
||||
'device_is_managed' => array_key_exists('isManaged', (array) $device) ? (int) ((bool) $device['isManaged']) : null,
|
||||
'status_error_code' => $this->nullableString(isset($status['errorCode']) ? (string) $status['errorCode'] : null),
|
||||
'status_failure_reason' => $this->nullableString($status['failureReason'] ?? null),
|
||||
'applied_policy_count' => $policyCount,
|
||||
'source_synced_at' => $now,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
|
||||
$policiesPerEvent[(int) $row->id] = [
|
||||
'cliente_id' => $clienteId,
|
||||
'm365_cred_id' => $m365CredId,
|
||||
'tenant_id' => $tenantId,
|
||||
'items' => $policyList,
|
||||
];
|
||||
|
||||
$progress->advance();
|
||||
}
|
||||
|
||||
DB::table('acesso_conditional_event_index')->upsert(
|
||||
$eventRows,
|
||||
['source_event_id'],
|
||||
[
|
||||
'cliente_id',
|
||||
'cliente_name',
|
||||
'm365_cred_id',
|
||||
'tenant_id',
|
||||
'source_payload_hash',
|
||||
'created_date_time',
|
||||
'user_display_name',
|
||||
'user_principal_name',
|
||||
'app_display_name',
|
||||
'ip_address',
|
||||
'conditional_access_status',
|
||||
'conditional_access_status_class',
|
||||
'executive_outcome_class',
|
||||
'location_city',
|
||||
'location_state',
|
||||
'location_country',
|
||||
'location_key',
|
||||
'device_display_name',
|
||||
'device_operating_system',
|
||||
'device_browser',
|
||||
'device_is_compliant',
|
||||
'device_is_managed',
|
||||
'status_error_code',
|
||||
'status_failure_reason',
|
||||
'applied_policy_count',
|
||||
'source_synced_at',
|
||||
'updated_at',
|
||||
]
|
||||
);
|
||||
|
||||
if (! $rebuildPolicies || $sourceIds === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$eventIndexMap = DB::table('acesso_conditional_event_index')
|
||||
->whereIn('source_event_id', $sourceIds)
|
||||
->pluck('id', 'source_event_id')
|
||||
->map(fn ($value) => (int) $value)
|
||||
->all();
|
||||
|
||||
$eventIndexIds = array_values($eventIndexMap);
|
||||
if ($eventIndexIds !== []) {
|
||||
DB::table('acesso_conditional_policy_index')->whereIn('event_index_id', $eventIndexIds)->delete();
|
||||
}
|
||||
|
||||
$policyRows = [];
|
||||
foreach ($policiesPerEvent as $sourceEventId => $payload) {
|
||||
$eventIndexId = $eventIndexMap[$sourceEventId] ?? null;
|
||||
if (! $eventIndexId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (($payload['items'] ?? []) as $policy) {
|
||||
$derived = $this->derivePolicyFlags((string) ($policy['result'] ?? ''));
|
||||
$policyRows[] = [
|
||||
'event_index_id' => $eventIndexId,
|
||||
'source_event_id' => $sourceEventId,
|
||||
'cliente_id' => $payload['cliente_id'] ?? null,
|
||||
'm365_cred_id' => $payload['m365_cred_id'] ?? null,
|
||||
'tenant_id' => $payload['tenant_id'] ?? null,
|
||||
'policy_id' => $this->nullableString($policy['id'] ?? null),
|
||||
'policy_name' => trim((string) ($policy['displayName'] ?? 'Sem nome')) ?: 'Sem nome',
|
||||
'policy_result' => $this->nullableString($policy['result'] ?? null),
|
||||
'policy_result_class' => $derived['class'],
|
||||
'is_report_only' => $derived['is_report_only'] ? 1 : 0,
|
||||
'grant_controls_json' => $this->jsonOrNull($policy['enforcedGrantControls'] ?? null),
|
||||
'session_controls_json' => $this->jsonOrNull($policy['enforcedSessionControls'] ?? null),
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_chunk($policyRows, 1000) as $chunkRows) {
|
||||
if ($chunkRows !== []) {
|
||||
DB::table('acesso_conditional_policy_index')->insert($chunkRows);
|
||||
}
|
||||
}
|
||||
}, 'id', 'id');
|
||||
|
||||
$progress->finish();
|
||||
$this->newLine(2);
|
||||
$this->info('Projeção concluída com sucesso.');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function decodeJson(mixed $value): mixed
|
||||
{
|
||||
if (is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (! is_string($value) || trim($value) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode($value, true);
|
||||
return json_last_error() === JSON_ERROR_NONE ? $decoded : null;
|
||||
}
|
||||
|
||||
private function nullableString(mixed $value): ?string
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim((string) $value);
|
||||
return $value !== '' ? $value : null;
|
||||
}
|
||||
|
||||
private function nullableUnsignedBigInt(mixed $value): ?int
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$value = trim((string) $value);
|
||||
if ($value === '' || ! preg_match('/^\d+$/', $value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parsed = (int) $value;
|
||||
return $parsed > 0 ? $parsed : null;
|
||||
}
|
||||
|
||||
private function jsonOrNull(mixed $value): ?string
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
private function buildLocationKey(?string $city, ?string $state, ?string $country): ?string
|
||||
{
|
||||
$parts = [mb_strtolower((string) $city), mb_strtolower((string) $state), mb_strtolower((string) $country)];
|
||||
$joined = trim(implode('|', $parts), '|');
|
||||
return $joined !== '' ? $joined : null;
|
||||
}
|
||||
|
||||
private function classifyAccessStatus(string $value): string
|
||||
{
|
||||
$flat = $this->normalizeFlat($value);
|
||||
if ($flat === '') {
|
||||
return 'unknown';
|
||||
}
|
||||
if (str_contains($flat, 'reportonly')) {
|
||||
return 'report-only';
|
||||
}
|
||||
if ($this->containsAny($flat, ['failure', 'falha', 'error', 'erro', 'block', 'blocked', 'bloque'])) {
|
||||
return 'failure';
|
||||
}
|
||||
if ($this->containsAny($flat, ['success', 'sucesso'])) {
|
||||
return 'success';
|
||||
}
|
||||
if ($this->containsAny($flat, ['notapplied', 'naoaplicado', 'nãoaplicado'])) {
|
||||
return 'not-applied';
|
||||
}
|
||||
if ($this->containsAny($flat, ['interrupt', 'interromp'])) {
|
||||
return 'interrupted';
|
||||
}
|
||||
return 'other';
|
||||
}
|
||||
|
||||
private function derivePolicyFlags(string $value): array
|
||||
{
|
||||
$flat = $this->normalizeFlat($value);
|
||||
if ($flat === '') {
|
||||
return ['class' => 'unknown', 'is_report_only' => false];
|
||||
}
|
||||
|
||||
$isReportOnly = str_contains($flat, 'reportonly');
|
||||
$class = 'other';
|
||||
|
||||
if ($this->containsAny($flat, ['failure', 'falha', 'error', 'erro', 'block', 'blocked', 'bloque'])) {
|
||||
$class = 'failure';
|
||||
} elseif ($this->containsAny($flat, ['success', 'sucesso'])) {
|
||||
$class = 'success';
|
||||
} elseif ($this->containsAny($flat, ['notapplied', 'naoaplicado', 'nãoaplicado'])) {
|
||||
$class = 'not-applied';
|
||||
} elseif ($this->containsAny($flat, ['interrupt', 'interromp'])) {
|
||||
$class = 'interrupted';
|
||||
} elseif ($isReportOnly) {
|
||||
$class = 'report-only';
|
||||
}
|
||||
|
||||
return ['class' => $class, 'is_report_only' => $isReportOnly];
|
||||
}
|
||||
|
||||
private function deriveEventOutcome(array $policies): array
|
||||
{
|
||||
$hasFailure = false;
|
||||
$hasWouldBlock = false;
|
||||
$hasSuccess = false;
|
||||
$hasNotApplied = false;
|
||||
$hasInterrupted = false;
|
||||
|
||||
foreach ($policies as $policy) {
|
||||
$derived = $this->derivePolicyFlags((string) ($policy['result'] ?? ''));
|
||||
if ($derived['class'] === 'failure' && $derived['is_report_only'] === false) {
|
||||
$hasFailure = true;
|
||||
}
|
||||
if ($derived['class'] === 'failure' && $derived['is_report_only'] === true) {
|
||||
$hasWouldBlock = true;
|
||||
}
|
||||
if ($derived['class'] === 'success') {
|
||||
$hasSuccess = true;
|
||||
}
|
||||
if ($derived['class'] === 'not-applied') {
|
||||
$hasNotApplied = true;
|
||||
}
|
||||
if ($derived['class'] === 'interrupted') {
|
||||
$hasInterrupted = true;
|
||||
}
|
||||
}
|
||||
|
||||
$outcome = 'other';
|
||||
if ($hasFailure) {
|
||||
$outcome = 'blocked';
|
||||
} elseif ($hasWouldBlock) {
|
||||
$outcome = 'would-block';
|
||||
} elseif ($hasSuccess) {
|
||||
$outcome = 'allowed';
|
||||
} elseif ($hasNotApplied) {
|
||||
$outcome = 'not-applied';
|
||||
} elseif ($hasInterrupted) {
|
||||
$outcome = 'interrupted';
|
||||
}
|
||||
|
||||
return [$outcome, count($policies)];
|
||||
}
|
||||
|
||||
private function normalizeFlat(string $value): string
|
||||
{
|
||||
$value = mb_strtolower(trim($value), 'UTF-8');
|
||||
$value = str_replace([' ', '-', '_'], '', $value);
|
||||
return iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value) ?: $value;
|
||||
}
|
||||
|
||||
private function containsAny(string $haystack, array $needles): bool
|
||||
{
|
||||
foreach ($needles as $needle) {
|
||||
if ($needle !== '' && str_contains($haystack, $needle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
10
app/Http/Controllers/AcessoCondicionalController.php
Normal file
10
app/Http/Controllers/AcessoCondicionalController.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class AcessoCondicionalController extends Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -10,7 +10,7 @@ class ClientesRelatorioController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$relatorios = clientes_relatorio::all();
|
||||
$relatorios = clientes_relatorio::paginate(10);
|
||||
return view("relatorios.index", compact('relatorios'));
|
||||
}
|
||||
|
||||
@@ -32,30 +32,39 @@ class ClientesRelatorioController extends Controller
|
||||
$ultimoRelatorio = clientes_relatorio::where('id_cliente', $clienteId)
|
||||
->latest('id')
|
||||
->first();
|
||||
try {
|
||||
if ($ultimoRelatorio) {
|
||||
$dadosNovoRelatorio = $ultimoRelatorio->toArray();
|
||||
|
||||
if ($ultimoRelatorio) {
|
||||
$dadosNovoRelatorio = $ultimoRelatorio->toArray();
|
||||
unset(
|
||||
$dadosNovoRelatorio['id'],
|
||||
$dadosNovoRelatorio['created_at'],
|
||||
$dadosNovoRelatorio['updated_at']
|
||||
);
|
||||
|
||||
unset(
|
||||
$dadosNovoRelatorio['id'],
|
||||
$dadosNovoRelatorio['created_at'],
|
||||
$dadosNovoRelatorio['updated_at']
|
||||
);
|
||||
// campos que você NÃO quer copiar
|
||||
unset(
|
||||
$dadosNovoRelatorio['status'],
|
||||
$dadosNovoRelatorio['data_envio'],
|
||||
$dadosNovoRelatorio['observacao_final']
|
||||
);
|
||||
|
||||
// campos que você NÃO quer copiar
|
||||
unset(
|
||||
$dadosNovoRelatorio['status'],
|
||||
$dadosNovoRelatorio['data_envio'],
|
||||
$dadosNovoRelatorio['observacao_final']
|
||||
);
|
||||
|
||||
$novoRelatorio = clientes_relatorio::create($dadosNovoRelatorio);
|
||||
} else {
|
||||
$novoRelatorio = clientes_relatorio::create($dadosNovoRelatorio);
|
||||
} else {
|
||||
$novoRelatorio = clientes_relatorio::create([
|
||||
'cliente_id' => $clienteId,
|
||||
]);
|
||||
'id_cliente' => $clienteId,
|
||||
'title' => 'Novo Relatório',
|
||||
'horas_utilizadas' => 0
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Relatório criado com sucesso.' . $ultimoRelatorio);
|
||||
} catch (\Throwable $th) {
|
||||
return redirect()->back()->with('error', 'Relatório não criado.' . $th->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->back()->with('success', 'Relatório criado com sucesso.');
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
1605
app/Http/Controllers/ConditionalAccessReportController.php
Normal file
1605
app/Http/Controllers/ConditionalAccessReportController.php
Normal file
File diff suppressed because it is too large
Load Diff
10
app/Models/acesso_condicional.php
Normal file
10
app/Models/acesso_condicional.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class acesso_condicional extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -45,4 +45,14 @@ class clientes extends Model
|
||||
{
|
||||
return $this->hasMany(clientes_contratos::class, 'cliente_id');
|
||||
}
|
||||
|
||||
public function chamados()
|
||||
{
|
||||
return $this->hasMany(clientes_chamado::class, 'id_cliente');
|
||||
}
|
||||
|
||||
public function relatorios()
|
||||
{
|
||||
return $this->hasMany(clientes_relatorio::class, 'id_cliente');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,19 @@ use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class clientes_relatorio extends Model
|
||||
{
|
||||
//
|
||||
protected $fillable = [
|
||||
'status',
|
||||
'title',
|
||||
'id_cliente',
|
||||
'horas_utilizadas',
|
||||
'chamados',
|
||||
'melhorias',
|
||||
'nota_consultor',
|
||||
'observacao_consultor'
|
||||
];
|
||||
|
||||
public function cliente()
|
||||
{
|
||||
return $this->hasOne(clientes::class,'id','id_cliente');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ return [
|
||||
]) : [],
|
||||
],
|
||||
|
||||
|
||||
'mariadb' => [
|
||||
'driver' => 'mariadb',
|
||||
'url' => env('DB_URL'),
|
||||
@@ -148,7 +149,7 @@ return [
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')).'-database-'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')) . '-database-'),
|
||||
'persistent' => env('REDIS_PERSISTENT', false),
|
||||
],
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('acesso_condicionals', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('nome_cliente');
|
||||
$table->string('tenant_id');
|
||||
$table->dateTime('chunk_start');
|
||||
$table->dateTime('chunk_end');
|
||||
$table->string('graph_id');
|
||||
$table->dateTime('created_date_time');
|
||||
$table->string('user_display_name');
|
||||
$table->string('user_principal_name');
|
||||
$table->string('app_display_name');
|
||||
$table->string('ip_address');
|
||||
$table->string('conditional_access_status');
|
||||
$table->json('location_json');
|
||||
$table->json('device_detail_json');
|
||||
$table->json('applied_policies_json');
|
||||
$table->json('status_json');
|
||||
$table->json('raw_json');
|
||||
$table->dateTime('imported_at');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('acesso_condicionals');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('acesso_condicionals', function (Blueprint $table) {
|
||||
$table->string('chunk_label')->after('tenant_id');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('acesso_condicionals', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('acesso_condicionals', function (Blueprint $table) {
|
||||
$table->index('created_date_time', 'idx_acesso_condicionals_created_date_time');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('acesso_condicionals', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_acesso_condicionals_created_date_time');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('acesso_condicionals', function (Blueprint $table) {
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'location_city')) {
|
||||
$table->string('location_city', 120)->nullable()->after('location_json');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'location_state')) {
|
||||
$table->string('location_state', 120)->nullable()->after('location_city');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'location_country')) {
|
||||
$table->string('location_country', 120)->nullable()->after('location_state');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'location_key')) {
|
||||
$table->string('location_key', 255)->nullable()->after('location_country');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'device_display_name')) {
|
||||
$table->string('device_display_name', 255)->nullable()->after('device_detail_json');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'device_operating_system')) {
|
||||
$table->string('device_operating_system', 120)->nullable()->after('device_display_name');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'device_browser')) {
|
||||
$table->string('device_browser', 120)->nullable()->after('device_operating_system');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'device_is_compliant')) {
|
||||
$table->boolean('device_is_compliant')->nullable()->after('device_browser');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'device_is_managed')) {
|
||||
$table->boolean('device_is_managed')->nullable()->after('device_is_compliant');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'status_error_code')) {
|
||||
$table->string('status_error_code', 40)->nullable()->after('status_json');
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('acesso_condicionals', 'status_failure_reason')) {
|
||||
$table->text('status_failure_reason')->nullable()->after('status_error_code');
|
||||
}
|
||||
});
|
||||
|
||||
Schema::table('acesso_condicionals', function (Blueprint $table) {
|
||||
$table->index('created_date_time', 'idx_ac_created_date_time');
|
||||
$table->index('conditional_access_status', 'idx_ac_status');
|
||||
$table->index('app_display_name', 'idx_ac_app');
|
||||
$table->index('user_principal_name', 'idx_ac_user_principal');
|
||||
$table->index('location_key', 'idx_ac_location_key');
|
||||
$table->index('device_operating_system', 'idx_ac_device_os');
|
||||
$table->index('device_browser', 'idx_ac_device_browser');
|
||||
$table->index('status_error_code', 'idx_ac_status_error_code');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('acesso_condicionals', function (Blueprint $table) {
|
||||
$table->dropIndex('idx_ac_created_date_time');
|
||||
$table->dropIndex('idx_ac_status');
|
||||
$table->dropIndex('idx_ac_app');
|
||||
$table->dropIndex('idx_ac_user_principal');
|
||||
$table->dropIndex('idx_ac_location_key');
|
||||
$table->dropIndex('idx_ac_device_os');
|
||||
$table->dropIndex('idx_ac_device_browser');
|
||||
$table->dropIndex('idx_ac_status_error_code');
|
||||
|
||||
$table->dropColumn([
|
||||
'location_city',
|
||||
'location_state',
|
||||
'location_country',
|
||||
'location_key',
|
||||
'device_display_name',
|
||||
'device_operating_system',
|
||||
'device_browser',
|
||||
'device_is_compliant',
|
||||
'device_is_managed',
|
||||
'status_error_code',
|
||||
'status_failure_reason',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('acesso_conditional_policies', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('acesso_conditional_id');
|
||||
$table->string('policy_id', 120)->nullable();
|
||||
$table->string('policy_name', 255);
|
||||
$table->string('policy_result', 120)->nullable();
|
||||
$table->json('grant_controls_json')->nullable();
|
||||
$table->json('session_controls_json')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('acesso_conditional_id', 'fk_ac_policy_parent')
|
||||
->references('id')
|
||||
->on('acesso_condicionals')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->index('acesso_conditional_id', 'idx_ac_policy_parent');
|
||||
$table->index('policy_name', 'idx_ac_policy_name');
|
||||
$table->index('policy_result', 'idx_ac_policy_result');
|
||||
$table->index(['policy_name', 'policy_result'], 'idx_ac_policy_name_result');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('acesso_conditional_policies');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('acesso_conditional_event_index', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('source_event_id')->unique();
|
||||
|
||||
$table->unsignedBigInteger('cliente_id')->nullable();
|
||||
$table->string('cliente_name', 255)->nullable();
|
||||
$table->unsignedBigInteger('m365_cred_id')->nullable();
|
||||
$table->string('tenant_id', 255)->nullable();
|
||||
|
||||
$table->char('source_payload_hash', 64)->nullable();
|
||||
$table->dateTime('created_date_time')->index('idx_acei_created_date_time');
|
||||
|
||||
$table->string('user_display_name', 255)->nullable();
|
||||
$table->string('user_principal_name', 255)->nullable();
|
||||
$table->string('app_display_name', 255)->nullable();
|
||||
$table->string('ip_address', 64)->nullable();
|
||||
|
||||
$table->string('conditional_access_status', 120)->nullable();
|
||||
$table->string('conditional_access_status_class', 40)->nullable();
|
||||
$table->string('executive_outcome_class', 40)->nullable();
|
||||
|
||||
$table->string('location_city', 120)->nullable();
|
||||
$table->string('location_state', 120)->nullable();
|
||||
$table->string('location_country', 120)->nullable();
|
||||
$table->string('location_key', 255)->nullable();
|
||||
|
||||
$table->string('device_display_name', 255)->nullable();
|
||||
$table->string('device_operating_system', 120)->nullable();
|
||||
$table->string('device_browser', 120)->nullable();
|
||||
$table->boolean('device_is_compliant')->nullable();
|
||||
$table->boolean('device_is_managed')->nullable();
|
||||
|
||||
$table->string('status_error_code', 40)->nullable();
|
||||
$table->text('status_failure_reason')->nullable();
|
||||
|
||||
$table->unsignedSmallInteger('applied_policy_count')->default(0);
|
||||
$table->timestamp('source_synced_at')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->index('cliente_id', 'idx_acei_cliente_id');
|
||||
$table->index('tenant_id', 'idx_acei_tenant_id');
|
||||
$table->index('m365_cred_id', 'idx_acei_m365_cred_id');
|
||||
$table->index(['cliente_id', 'tenant_id'], 'idx_acei_cliente_tenant');
|
||||
$table->index(['cliente_id', 'created_date_time'], 'idx_acei_cliente_created');
|
||||
$table->index(['tenant_id', 'created_date_time'], 'idx_acei_tenant_created');
|
||||
$table->index(['cliente_id', 'tenant_id', 'created_date_time'], 'idx_acei_cliente_tenant_created');
|
||||
|
||||
$table->index('conditional_access_status', 'idx_acei_status');
|
||||
$table->index('conditional_access_status_class', 'idx_acei_status_class');
|
||||
$table->index('executive_outcome_class', 'idx_acei_exec_outcome');
|
||||
$table->index('app_display_name', 'idx_acei_app');
|
||||
$table->index('user_principal_name', 'idx_acei_user_principal');
|
||||
$table->index('location_key', 'idx_acei_location_key');
|
||||
$table->index('device_operating_system', 'idx_acei_device_os');
|
||||
$table->index('device_browser', 'idx_acei_device_browser');
|
||||
$table->index('device_is_compliant', 'idx_acei_device_compliant');
|
||||
$table->index('device_is_managed', 'idx_acei_device_managed');
|
||||
$table->index('status_error_code', 'idx_acei_status_error_code');
|
||||
|
||||
$table->index(['created_date_time', 'conditional_access_status_class'], 'idx_acei_created_status_class');
|
||||
$table->index(['created_date_time', 'executive_outcome_class'], 'idx_acei_created_exec_outcome');
|
||||
$table->index(['created_date_time', 'app_display_name'], 'idx_acei_created_app');
|
||||
$table->index(['created_date_time', 'device_operating_system'], 'idx_acei_created_os');
|
||||
$table->index(['created_date_time', 'device_browser'], 'idx_acei_created_browser');
|
||||
$table->index(['cliente_id', 'created_date_time', 'app_display_name'], 'idx_acei_cliente_created_app');
|
||||
$table->index(['cliente_id', 'created_date_time', 'device_operating_system'], 'idx_acei_cliente_created_os');
|
||||
$table->index(['tenant_id', 'created_date_time', 'conditional_access_status_class'], 'idx_acei_tenant_created_status');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('acesso_conditional_event_index');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('acesso_conditional_policy_index', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('event_index_id');
|
||||
$table->unsignedBigInteger('source_event_id');
|
||||
|
||||
$table->unsignedBigInteger('cliente_id')->nullable();
|
||||
$table->unsignedBigInteger('m365_cred_id')->nullable();
|
||||
$table->string('tenant_id', 255)->nullable();
|
||||
|
||||
$table->string('policy_id', 120)->nullable();
|
||||
$table->string('policy_name', 255);
|
||||
$table->string('policy_result', 120)->nullable();
|
||||
$table->string('policy_result_class', 40)->nullable();
|
||||
$table->boolean('is_report_only')->default(false);
|
||||
$table->json('grant_controls_json')->nullable();
|
||||
$table->json('session_controls_json')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('event_index_id', 'fk_acpi_event_index')
|
||||
->references('id')
|
||||
->on('acesso_conditional_event_index')
|
||||
->cascadeOnDelete();
|
||||
|
||||
$table->index('event_index_id', 'idx_acpi_event_index');
|
||||
$table->index('source_event_id', 'idx_acpi_source_event');
|
||||
$table->index('cliente_id', 'idx_acpi_cliente_id');
|
||||
$table->index('tenant_id', 'idx_acpi_tenant_id');
|
||||
$table->index('m365_cred_id', 'idx_acpi_m365_cred_id');
|
||||
$table->index(['cliente_id', 'tenant_id'], 'idx_acpi_cliente_tenant');
|
||||
$table->index(['cliente_id', 'event_index_id'], 'idx_acpi_cliente_event');
|
||||
$table->index(['tenant_id', 'event_index_id'], 'idx_acpi_tenant_event');
|
||||
$table->index('policy_name', 'idx_acpi_policy_name');
|
||||
$table->index('policy_result', 'idx_acpi_policy_result');
|
||||
$table->index('policy_result_class', 'idx_acpi_result_class');
|
||||
$table->index('is_report_only', 'idx_acpi_report_only');
|
||||
$table->index(['policy_name', 'policy_result'], 'idx_acpi_policy_name_result');
|
||||
$table->index(['policy_name', 'policy_result_class'], 'idx_acpi_policy_name_result_class');
|
||||
$table->index(['event_index_id', 'policy_name'], 'idx_acpi_event_policy_name');
|
||||
$table->index(['cliente_id', 'policy_name'], 'idx_acpi_cliente_policy_name');
|
||||
$table->index(['cliente_id', 'policy_result_class'], 'idx_acpi_cliente_result_class');
|
||||
$table->index(['tenant_id', 'policy_name'], 'idx_acpi_tenant_policy_name');
|
||||
$table->index(['tenant_id', 'policy_result_class'], 'idx_acpi_tenant_result_class');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('acesso_conditional_policy_index');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('clientes_relatorios', function (Blueprint $table) {
|
||||
$table->string('status')->default('Novo')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('clientes_relatorios', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('clientes_relatorios', function (Blueprint $table) {
|
||||
$table->string('chamados')->nullable()->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('clientes_relatorios', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -123,7 +123,8 @@
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white border-0 pb-0">
|
||||
<h5 class="mb-0">Utilização dos Contratos</h5>
|
||||
<small class="text-muted">Acompanhe o consumo de horas de cada contrato</small>
|
||||
<small class="text-muted">Acompanhe o consumo de horas de cada contrato
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
@@ -138,7 +139,7 @@
|
||||
<tbody>
|
||||
@foreach($cliente->contratos as $contrato)
|
||||
@php
|
||||
$horas_utilizadas = 140; // valor fixo por enquanto
|
||||
$horas_utilizadas = $cliente->chamados->sum('minutos_utilizados') / 60;
|
||||
$horas_contratadas = $contrato->horas_contratadas ?? 0;
|
||||
|
||||
$porcentagem_utilizada = $horas_contratadas > 0
|
||||
|
||||
@@ -1,331 +1,396 @@
|
||||
@extends('theme.default')
|
||||
@section('page.title', 'Clientes')
|
||||
|
||||
@section('content')
|
||||
@include('theme.alertas')
|
||||
<!-- End - Page Title & Breadcrumb -->
|
||||
@include('theme.alertas')
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xl-12 bst-seller">
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
<h4 class="mb-0">Lista de CLientes</h4>
|
||||
<div class="d-flex align-items-center">
|
||||
<a class="btn btn-primary btn-sm ms-2" data-bs-toggle="offcanvas" href="#addTaskmodal" role="button"
|
||||
aria-controls="addTaskmodal">+ Cliente</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card h-auto">
|
||||
<div class="card-header border-0 align-items-center">
|
||||
<div id="tableCustomerFilter"></div>
|
||||
<div id="tableCustomerExcelBTN"></div>
|
||||
</div>
|
||||
<div class="card-body table-card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive check-wrapper">
|
||||
<table id="customerTable" class="table">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="mw-100">ID</th>
|
||||
<th class="mw-200">Nome</th>
|
||||
<th class="mw-100">CNPJ</th>
|
||||
<th class="mw-100"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($clientes as $cliente)
|
||||
<tr>
|
||||
<td><span>{{ $cliente->id }}</span></td>
|
||||
<td>
|
||||
<div class="d-flex">
|
||||
<img src="{{ $cliente->logotipo }}" class="avatar avatar-sm me-2" alt="">
|
||||
<div class="clearfix">
|
||||
<h6 class="mb-0"><a
|
||||
href="{{ route('cliente', $cliente->id) }}">{{ $cliente->name }}</a>
|
||||
</h6>
|
||||
<small>{{ $cliente->endereco['logradouro'] }},
|
||||
{{ $cliente->endereco['numero'] }}
|
||||
@if (isset($cliente->endereco['complemento']))
|
||||
({{ $cliente->endereco['complemento'] }}) -
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
{{ $cliente->endereco['cep'] }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><a class="text-primary">{{ $cliente->cnpj }}</a></td>
|
||||
<td class="text-end">
|
||||
<div class="dropdown">
|
||||
<button type="button" class="btn btn-sm btn-primary light btn-square"
|
||||
data-bs-toggle="dropdown">
|
||||
<i class="fa-solid fa-ellipsis"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item" data-bs-toggle="offcanvas"
|
||||
href="#editar-cliente-{{ $cliente->id }}"
|
||||
aria-controls="editar-cliente-{{ $cliente->id }}">Editar</a>
|
||||
</li>
|
||||
<li><a class="dropdown-item text-danger" data-bs-toggle="offcanvas"
|
||||
href="#deletar-cliente-{{ $cliente->id }}"
|
||||
aria-controls="deletar-cliente-{{ $cliente->id }}">Deletar</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- MODAL DE EDIÇÃO -->
|
||||
<div class="offcanvas offcanvas-end custom-offcanvas" tabindex="-1"
|
||||
id="editar-cliente-{{ $cliente->id }}">
|
||||
<div class="offcanvas-header pb-0">
|
||||
<h2 class="modal-title fs-5" id="#gridSystemModal1">Editar {{ $cliente->name }}
|
||||
</h2>
|
||||
<button type="button" class="btn btn-square btn-danger light btn-sm ms-auto"
|
||||
data-bs-dismiss="offcanvas" aria-label="Close">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form class="row" action="{{ route('clientes.edit', $cliente->id) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">Nome do
|
||||
Cliente</label>
|
||||
<input type="text" class="form-control" id="name" name="name"
|
||||
value="{{ $cliente->name }}" required>
|
||||
</div>
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label for="exampleFormControlInputfirst"
|
||||
class="form-label">CNPJ</label>
|
||||
<input type="text" class="form-control" id="cnpj_editar" name="cnpj"
|
||||
value="{{ $cliente->cnpj }}" required maxlength="18">
|
||||
</div>
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label for="exampleFormControlInputfirst"
|
||||
class="form-label">Endereço</label>
|
||||
<input type="text" class="form-control" id="logradouro"
|
||||
name="logradouro" value="{{ $cliente->endereco['logradouro'] }}"
|
||||
required>
|
||||
</div>
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label for="exampleFormControlInputfirst"
|
||||
class="form-label">Número</label>
|
||||
<input type="text" class="form-control" id="logradouro_numero"
|
||||
name="logradouro_numero" value="{{ $cliente->endereco['numero'] }}"
|
||||
required>
|
||||
</div>
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label for="exampleFormControlInputfirst"
|
||||
class="form-label">Complemento</label>
|
||||
<input type="text" class="form-control" id="logradouro_complemento"
|
||||
name="logradouro_complemento"
|
||||
value="{{ $cliente->endereco['complemento'] }}">
|
||||
</div>
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">CEP</label>
|
||||
<input type="text" class="form-control" id="logradouro_cep_editar"
|
||||
name="logradouro_cep" value="{{ $cliente->endereco['cep'] }}"
|
||||
required maxlength="9">
|
||||
</div>
|
||||
<div class="col-xl-12">
|
||||
<button class="btn btn-danger light">Cancel</button>
|
||||
<button type="submit" class="btn btn-warning ms-2">Atualizar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid d-flex flex-column min-vh-100">
|
||||
<div class="row flex-grow-1">
|
||||
<div class="col-12 d-flex flex-column bst-seller">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Clientes</h4>
|
||||
<p class="mb-0 text-muted">Gerencie os clientes cadastrados</p>
|
||||
</div>
|
||||
|
||||
<!-- -->
|
||||
<div class="offcanvas offcanvas-end custom-offcanvas" tabindex="-1"
|
||||
id="deletar-cliente-{{ $cliente->id }}">
|
||||
<div class="offcanvas-header pb-0">
|
||||
<h2 class="modal-title fs-5" id="#gridSystemModal1">Editar {{ $cliente->name }}
|
||||
</h2>
|
||||
<button type="button" class="btn btn-square btn-danger light btn-sm ms-auto"
|
||||
data-bs-dismiss="offcanvas" aria-label="Close">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form class="row" action="{{ route('clientes.delete', $cliente->id) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">Você tem
|
||||
certeza que quer deletar ?</label>
|
||||
</div>
|
||||
<div class="col-xl-12">
|
||||
<button class="btn btn-danger light">Cancelar</button>
|
||||
<button type="submit" class="btn btn-danger ms-2">Deletar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="d-flex align-items-center">
|
||||
<a class="btn btn-primary btn-sm ms-2" data-bs-toggle="offcanvas" href="#addTaskmodal"
|
||||
role="button" aria-controls="addTaskmodal">
|
||||
+ Cliente
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card flex-grow-1 overflow-visible d-flex flex-column">
|
||||
<div class="card-header border-0 d-flex flex-wrap align-items-center justify-content-between gap-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Lista de clientes</h5>
|
||||
</div>
|
||||
|
||||
<!-- modal -->
|
||||
<div class="offcanvas offcanvas-end custom-offcanvas" tabindex="-1" id="addTaskmodal">
|
||||
<div class="offcanvas-header pb-0">
|
||||
<h2 class="modal-title fs-5" id="#gridSystemModal1">Novo Cliente</h2>
|
||||
<button type="button" class="btn btn-square btn-danger light btn-sm ms-auto" data-bs-dismiss="offcanvas"
|
||||
aria-label="Close">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form class="row" action="{{ route('clientes.add') }}" method="POST">
|
||||
@csrf
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">Nome do Cliente</label>
|
||||
<input type="text" class="form-control" id="name" name="name" placeholder="Nome do Cliente" required>
|
||||
</div>
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">CNPJ</label>
|
||||
<input type="text" class="form-control" id="cnpj_novo" name="cnpj" placeholder="CNPJ" required
|
||||
maxlength="18">
|
||||
</div>
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">Endereço</label>
|
||||
<input type="text" class="form-control" id="logradouro" name="logradouro" placeholder="Endereço"
|
||||
required>
|
||||
</div>
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">Número</label>
|
||||
<input type="text" class="form-control" id="logradouro_numero" name="logradouro_numero"
|
||||
placeholder="Número" required>
|
||||
</div>
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">Complemento</label>
|
||||
<input type="text" class="form-control" id="logradouro_complemento" name="logradouro_complemento"
|
||||
placeholder="Complemento">
|
||||
</div>
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label for="exampleFormControlInputfirst" class="form-label">CEP</label>
|
||||
<input type="text" class="form-control" id="logradouro_cep_novo" name="logradouro_cep" placeholder="CEP"
|
||||
required maxlength="9">
|
||||
</div>
|
||||
<div class="col-xl-12">
|
||||
<button data-bs-dismiss="offcanvas" class="btn btn-danger light">Cancelar</button>
|
||||
<button type="submit" class="btn btn-primary ms-2">Cadastrar</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination justify-content-center">
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
<div id="tableCustomerFilter"></div>
|
||||
<div id="tableCustomerExcelBTN"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Previous --}}
|
||||
<li class="page-item {{ $clientes->onFirstPage() ? 'disabled' : '' }}">
|
||||
<a class="page-link" href="{{ $clientes->previousPageUrl() ?? '#' }}">Anterior</a>
|
||||
</li>
|
||||
<div class="card-body table-card-body px-0 pt-0 pb-2 overflow-visible d-flex flex-column">
|
||||
<div class="table-responsive check-wrapper table-responsive-dropdown flex-grow-1">
|
||||
<table id="customerTable" class="table align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="mw-100">ID</th>
|
||||
<th class="mw-200">Nome</th>
|
||||
<th class="mw-100">CNPJ</th>
|
||||
<th class="mw-100 text-end pe-4">Opções</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@php
|
||||
$current = $clientes->currentPage();
|
||||
$last = $clientes->lastPage();
|
||||
$start = max(1, $current - 2);
|
||||
$end = min($last, $current + 2);
|
||||
<tbody>
|
||||
@forelse ($clientes as $cliente)
|
||||
<tr>
|
||||
<td>
|
||||
<span>{{ $cliente->id }}</span>
|
||||
</td>
|
||||
|
||||
// Ajustar quando estiver no começo
|
||||
if ($current <= 3) {
|
||||
$start = 1;
|
||||
$end = min(5, $last);
|
||||
}
|
||||
<td>
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="{{ $cliente->logotipo }}" class="avatar avatar-sm me-3 rounded"
|
||||
alt="Logo do cliente">
|
||||
|
||||
// Ajustar quando estiver no final
|
||||
if ($current > $last - 3) {
|
||||
$start = max(1, $last - 4);
|
||||
$end = $last;
|
||||
}
|
||||
@endphp
|
||||
<div class="clearfix">
|
||||
<h6 class="mb-1">
|
||||
<a href="{{ route('cliente', $cliente->id) }}">
|
||||
{{ $cliente->name }}
|
||||
</a>
|
||||
</h6>
|
||||
|
||||
{{-- "..." antes --}}
|
||||
@if ($start > 1)
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $clientes->url(1) }}">1</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link">...</a>
|
||||
</li>
|
||||
@endif
|
||||
<small class="text-muted">
|
||||
{{ $cliente->endereco['logradouro'] ?? '' }},
|
||||
{{ $cliente->endereco['numero'] ?? '' }}
|
||||
@if (isset($cliente->endereco['complemento']) && !empty($cliente->endereco['complemento']))
|
||||
({{ $cliente->endereco['complemento'] }}) -
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
{{ $cliente->endereco['cep'] ?? '' }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
{{-- Laço das páginas --}}
|
||||
@for ($i = $start; $i <= $end; $i++)
|
||||
<li class="page-item {{ $current == $i ? 'active' : '' }}">
|
||||
<a class="page-link" href="{{ $clientes->url($i) }}">{{ $i }}</a>
|
||||
</li>
|
||||
@endfor
|
||||
<td>
|
||||
<a class="text-primary">{{ $cliente->cnpj }}</a>
|
||||
</td>
|
||||
|
||||
{{-- "..." depois --}}
|
||||
@if ($end < $last)
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link">...</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $clientes->url($last) }}">{{ $last }}</a>
|
||||
</li>
|
||||
@endif
|
||||
<td class="text-end pe-4 td-options">
|
||||
<div class="dropdown">
|
||||
<button type="button"
|
||||
class="btn btn-sm btn-primary light btn-square"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-ellipsis"></i>
|
||||
</button>
|
||||
|
||||
{{-- Next --}}
|
||||
<li class="page-item {{ $clientes->hasMorePages() ? '' : 'disabled' }}">
|
||||
<a class="page-link" href="{{ $clientes->nextPageUrl() ?? '#' }}">Próximo</a>
|
||||
</li>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<a class="dropdown-item" data-bs-toggle="offcanvas"
|
||||
href="#editar-cliente-{{ $cliente->id }}"
|
||||
aria-controls="editar-cliente-{{ $cliente->id }}">
|
||||
Editar
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item text-danger"
|
||||
data-bs-toggle="offcanvas"
|
||||
href="#deletar-cliente-{{ $cliente->id }}"
|
||||
aria-controls="deletar-cliente-{{ $cliente->id }}">
|
||||
Deletar
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="offcanvas offcanvas-end custom-offcanvas" tabindex="-1"
|
||||
id="editar-cliente-{{ $cliente->id }}">
|
||||
<div class="offcanvas-header pb-0">
|
||||
<h2 class="modal-title fs-5">Editar {{ $cliente->name }}</h2>
|
||||
<button type="button"
|
||||
class="btn btn-square btn-danger light btn-sm ms-auto"
|
||||
data-bs-dismiss="offcanvas" aria-label="Close">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form class="row" action="{{ route('clientes.edit', $cliente->id) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label class="form-label">Nome do Cliente</label>
|
||||
<input type="text" class="form-control" name="name"
|
||||
value="{{ $cliente->name }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label class="form-label">CNPJ</label>
|
||||
<input type="text" class="form-control cnpj-editar" name="cnpj"
|
||||
value="{{ $cliente->cnpj }}" required maxlength="18">
|
||||
</div>
|
||||
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label class="form-label">Endereço</label>
|
||||
<input type="text" class="form-control" name="logradouro"
|
||||
value="{{ $cliente->endereco['logradouro'] }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label class="form-label">Número</label>
|
||||
<input type="text" class="form-control"
|
||||
name="logradouro_numero"
|
||||
value="{{ $cliente->endereco['numero'] }}" required>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label class="form-label">Complemento</label>
|
||||
<input type="text" class="form-control"
|
||||
name="logradouro_complemento"
|
||||
value="{{ $cliente->endereco['complemento'] }}">
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label class="form-label">CEP</label>
|
||||
<input type="text" class="form-control cep-editar"
|
||||
name="logradouro_cep"
|
||||
value="{{ $cliente->endereco['cep'] }}" required
|
||||
maxlength="9">
|
||||
</div>
|
||||
|
||||
<div class="col-xl-12">
|
||||
<button type="button" class="btn btn-danger light"
|
||||
data-bs-dismiss="offcanvas">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="submit" class="btn btn-warning ms-2">
|
||||
Atualizar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas offcanvas-end custom-offcanvas" tabindex="-1"
|
||||
id="deletar-cliente-{{ $cliente->id }}">
|
||||
<div class="offcanvas-header pb-0">
|
||||
<h2 class="modal-title fs-5">Deletar {{ $cliente->name }}</h2>
|
||||
<button type="button"
|
||||
class="btn btn-square btn-danger light btn-sm ms-auto"
|
||||
data-bs-dismiss="offcanvas" aria-label="Close">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form class="row" action="{{ route('clientes.delete', $cliente->id) }}"
|
||||
method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label class="form-label">
|
||||
Você tem certeza que quer deletar?
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-12">
|
||||
<button type="button" class="btn btn-danger light"
|
||||
data-bs-dismiss="offcanvas">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="submit" class="btn btn-danger ms-2">
|
||||
Deletar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="text-center py-5 text-muted">
|
||||
Nenhum cliente encontrado.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination justify-content-center mb-0">
|
||||
|
||||
<li class="page-item {{ $clientes->onFirstPage() ? 'disabled' : '' }}">
|
||||
<a class="page-link" href="{{ $clientes->previousPageUrl() ?? '#' }}">Anterior</a>
|
||||
</li>
|
||||
|
||||
@php
|
||||
$current = $clientes->currentPage();
|
||||
$last = $clientes->lastPage();
|
||||
$start = max(1, $current - 2);
|
||||
$end = min($last, $current + 2);
|
||||
|
||||
if ($current <= 3) {
|
||||
$start = 1;
|
||||
$end = min(5, $last);
|
||||
}
|
||||
|
||||
if ($current > $last - 3) {
|
||||
$start = max(1, $last - 4);
|
||||
$end = $last;
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if ($start > 1)
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $clientes->url(1) }}">1</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link">...</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@for ($i = $start; $i <= $end; $i++)
|
||||
<li class="page-item {{ $current == $i ? 'active' : '' }}">
|
||||
<a class="page-link" href="{{ $clientes->url($i) }}">{{ $i }}</a>
|
||||
</li>
|
||||
@endfor
|
||||
|
||||
@if ($end < $last)
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link">...</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $clientes->url($last) }}">{{ $last }}</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li class="page-item {{ $clientes->hasMorePages() ? '' : 'disabled' }}">
|
||||
<a class="page-link" href="{{ $clientes->nextPageUrl() ?? '#' }}">Próximo</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="offcanvas offcanvas-end custom-offcanvas" tabindex="-1" id="addTaskmodal">
|
||||
<div class="offcanvas-header pb-0">
|
||||
<h2 class="modal-title fs-5">Novo Cliente</h2>
|
||||
<button type="button" class="btn btn-square btn-danger light btn-sm ms-auto"
|
||||
data-bs-dismiss="offcanvas" aria-label="Close">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
<form class="row" action="{{ route('clientes.add') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label class="form-label">Nome do Cliente</label>
|
||||
<input type="text" class="form-control" name="name" placeholder="Nome do Cliente" required>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-12 mb-3">
|
||||
<label class="form-label">CNPJ</label>
|
||||
<input type="text" class="form-control" id="cnpj_novo" name="cnpj" placeholder="CNPJ" required
|
||||
maxlength="18">
|
||||
</div>
|
||||
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label class="form-label">Endereço</label>
|
||||
<input type="text" class="form-control" name="logradouro" placeholder="Endereço" required>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label class="form-label">Número</label>
|
||||
<input type="text" class="form-control" name="logradouro_numero" placeholder="Número" required>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-8 mb-3">
|
||||
<label class="form-label">Complemento</label>
|
||||
<input type="text" class="form-control" name="logradouro_complemento"
|
||||
placeholder="Complemento">
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 mb-3">
|
||||
<label class="form-label">CEP</label>
|
||||
<input type="text" class="form-control" id="logradouro_cep_novo" name="logradouro_cep"
|
||||
placeholder="CEP" required maxlength="9">
|
||||
</div>
|
||||
|
||||
<div class="col-xl-12">
|
||||
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-danger light">
|
||||
Cancelar
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary ms-2">
|
||||
Cadastrar
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('styles')
|
||||
<style>
|
||||
.table-responsive-dropdown {
|
||||
overflow-x: auto;
|
||||
overflow-y: visible !important;
|
||||
}
|
||||
|
||||
.card.overflow-visible,
|
||||
.card-body.overflow-visible {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.td-options {
|
||||
position: relative;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.td-options .dropdown-menu {
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
document.getElementById('cnpj_novo').addEventListener('input', function () {
|
||||
let v = this.value.replace(/\D/g, '');
|
||||
<script>
|
||||
function aplicarMascaraCnpj(valor) {
|
||||
let v = valor.replace(/\D/g, '');
|
||||
v = v.replace(/^(\d{2})(\d)/, '$1.$2');
|
||||
v = v.replace(/^(\d{2})\.(\d{3})(\d)/, '$1.$2.$3');
|
||||
v = v.replace(/\.(\d{3})(\d)/, '.$1/$2');
|
||||
v = v.replace(/(\d{4})(\d)/, '$1-$2');
|
||||
return v;
|
||||
}
|
||||
|
||||
// Monta a máscara
|
||||
v = v.replace(/^(\d{2})(\d)/, "$1.$2");
|
||||
v = v.replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3");
|
||||
v = v.replace(/\.(\d{3})(\d)/, ".$1/$2");
|
||||
v = v.replace(/(\d{4})(\d)/, "$1-$2");
|
||||
function aplicarMascaraCep(valor) {
|
||||
let v = valor.replace(/\D/g, '');
|
||||
v = v.replace(/^(\d{5})(\d)/, '$1-$2');
|
||||
return v;
|
||||
}
|
||||
|
||||
this.value = v;
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('cnpj_editar').addEventListener('input', function () {
|
||||
let v = this.value.replace(/\D/g, '');
|
||||
|
||||
// Monta a máscara
|
||||
v = v.replace(/^(\d{2})(\d)/, "$1.$2");
|
||||
v = v.replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3");
|
||||
v = v.replace(/\.(\d{3})(\d)/, ".$1/$2");
|
||||
v = v.replace(/(\d{4})(\d)/, "$1-$2");
|
||||
|
||||
this.value = v;
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('logradouro_cep_novo').addEventListener('input', function () {
|
||||
let v = this.value.replace(/\D/g, '');
|
||||
|
||||
v = v.replace(/^(\d{5})(\d)/, "$1-$2");
|
||||
|
||||
this.value = v;
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
document.getElementById('logradouro_cep_editar').addEventListener('input', function () {
|
||||
let v = this.value.replace(/\D/g, '');
|
||||
|
||||
v = v.replace(/^(\d{5})(\d)/, "$1-$2");
|
||||
|
||||
this.value = v;
|
||||
});
|
||||
</script>
|
||||
document.addEventListener('input', function(e) {
|
||||
if (e.target.id === 'cnpj_novo' || e.target.classList.contains('cnpj-editar')) {
|
||||
e.target.value = aplicarMascaraCnpj(e.target.value);
|
||||
}
|
||||
|
||||
if (e.target.id === 'logradouro_cep_novo' || e.target.classList.contains('cep-editar')) {
|
||||
e.target.value = aplicarMascaraCep(e.target.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -1,102 +1,219 @@
|
||||
@extends('theme.default')
|
||||
@section('page.title', 'Relatórios')
|
||||
|
||||
@section('content')
|
||||
@include('theme.alertas')
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xl-12 bst-seller">
|
||||
<div class="d-flex align-items-center justify-content-between mb-4">
|
||||
|
||||
<div class="container-fluid d-flex flex-column min-vh-100">
|
||||
<div class="row flex-grow-1">
|
||||
<div class="col-12 d-flex flex-column bst-seller">
|
||||
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
||||
<div>
|
||||
<h4 class="mb-1">Relatórios</h4>
|
||||
<p class="mb-0 text-muted">Gerencie os relatórios cadastrados</p>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<!-- Button trigger modal -->
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal">
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal"
|
||||
data-bs-target="#exampleModal">
|
||||
+ Add Relatório
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ route('relatorios.store') }}" method="POST">
|
||||
@csrf
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form action="{{ route('relatorios.store') }}" method="POST">
|
||||
@csrf
|
||||
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="exampleModalLabel">Novo Relatório</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="cliente_id" class="form-label">Cliente</label>
|
||||
<select name="cliente_id" id="cliente_id" class="form-select" required>
|
||||
<option value="">Carregando clientes...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary"
|
||||
data-bs-dismiss="modal">Fechar</button>
|
||||
<button type="submit" class="btn btn-primary">Criar relatório</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="exampleModalLabel">Novo Relatório</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||
aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label for="cliente_id" class="form-label">Cliente</label>
|
||||
<select name="cliente_id" id="cliente_id" class="form-select" required>
|
||||
<option value="">Carregando clientes...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary"
|
||||
data-bs-dismiss="modal">Fechar</button>
|
||||
<button type="submit" class="btn btn-primary">Criar relatório</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card h-auto">
|
||||
<div class="card-header border-0 align-items-center">
|
||||
<div id="tableCustomerFilter"></div>
|
||||
<div id="tableCustomerExcelBTN"></div>
|
||||
|
||||
<div class="card flex-grow-1 overflow-visible d-flex flex-column">
|
||||
<div class="card-header border-0 d-flex flex-wrap align-items-center justify-content-between gap-3">
|
||||
<div>
|
||||
<h5 class="mb-0">Lista de relatórios</h5>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap align-items-center gap-2">
|
||||
<div id="tableCustomerFilter"></div>
|
||||
<div id="tableCustomerExcelBTN"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body table-card-body px-0 pt-0 pb-2">
|
||||
<div class="table-responsive check-wrapper">
|
||||
<table id="customerTable" class="table">
|
||||
|
||||
<div class="card-body table-card-body px-0 pt-0 pb-2 overflow-visible d-flex flex-column">
|
||||
<div class="table-responsive check-wrapper table-responsive-dropdown flex-grow-1">
|
||||
<table id="customerTable" class="table align-middle mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th class="mw-100">ID</th>
|
||||
<th class="mw-200">Título</th>
|
||||
<th class="mw-100">Status</th>
|
||||
<th class="mw-100">Opções</th>
|
||||
<th class="mw-100 text-end pe-4">Opções</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@foreach ($relatorios as $relatorio)
|
||||
@forelse ($relatorios as $relatorio)
|
||||
<tr>
|
||||
<td><span>{{$relatorio->id}}</span></td>
|
||||
<td>
|
||||
<div class="d-flex">
|
||||
<img src="assets/images/avatar/small/avatar1.webp"
|
||||
class="avatar avatar-sm me-2" alt="">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="@if (isset($relatorio->cliente['logotipo']) && !empty($relatorio->cliente['logotipo']))
|
||||
{{ $relatorio->cliente['logotipo'] }}
|
||||
@else
|
||||
https://cloudessential.tech/assets/logo-cloud-essential-D_wtjG9m.png
|
||||
@endif"
|
||||
class="avatar avatar-sm me-3 rounded" alt="Logo do cliente">
|
||||
|
||||
<div class="clearfix">
|
||||
<h6 class="mb-0">{{$relatorio->titulo}}</h6>
|
||||
<h6 class="mb-1">{{ $relatorio->title }}</h6>
|
||||
<span class="text-muted">
|
||||
@if (isset($relatorio->cliente['name']) && !empty($relatorio->cliente['name']))
|
||||
{{ $relatorio->cliente['name'] }}
|
||||
@else
|
||||
Sem cliente associado
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge badge-success light">{{$relatorio->status}}</span></td>
|
||||
<td><span></span></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
||||
<td>
|
||||
<span class="badge badge-success light">
|
||||
{{ $relatorio->status }}
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="td-options text-end pe-4">
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-primary light sharp" type="button"
|
||||
data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Ações
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<a class="dropdown-item" href="javascript:void(0);">
|
||||
Editar Relatório
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item text-danger"
|
||||
href="javascript:void(0);">
|
||||
Apagar Relatório
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="text-center py-5 text-muted">
|
||||
Nenhum relatório encontrado.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<nav aria-label="Page navigation example">
|
||||
<ul class="pagination justify-content-center mb-0">
|
||||
|
||||
<li class="page-item {{ $relatorios->onFirstPage() ? 'disabled' : '' }}">
|
||||
<a class="page-link" href="{{ $relatorios->previousPageUrl() ?? '#' }}">Anterior</a>
|
||||
</li>
|
||||
|
||||
@php
|
||||
$current = $relatorios->currentPage();
|
||||
$last = $relatorios->lastPage();
|
||||
$start = max(1, $current - 2);
|
||||
$end = min($last, $current + 2);
|
||||
|
||||
if ($current <= 3) {
|
||||
$start = 1;
|
||||
$end = min(5, $last);
|
||||
}
|
||||
|
||||
if ($current > $last - 3) {
|
||||
$start = max(1, $last - 4);
|
||||
$end = $last;
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if ($start > 1)
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $relatorios->url(1) }}">1</a>
|
||||
</li>
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link">...</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
@for ($i = $start; $i <= $end; $i++)
|
||||
<li class="page-item {{ $current == $i ? 'active' : '' }}">
|
||||
<a class="page-link" href="{{ $relatorios->url($i) }}">{{ $i }}</a>
|
||||
</li>
|
||||
@endfor
|
||||
|
||||
@if ($end < $last)
|
||||
<li class="page-item disabled">
|
||||
<a class="page-link">...</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="{{ $relatorios->url($last) }}">{{ $last }}</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li class="page-item {{ $relatorios->hasMorePages() ? '' : 'disabled' }}">
|
||||
<a class="page-link" href="{{ $relatorios->nextPageUrl() ?? '#' }}">Próximo</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const modal = document.getElementById('exampleModal');
|
||||
const select = document.getElementById('cliente_id');
|
||||
|
||||
modal.addEventListener('show.bs.modal', function () {
|
||||
modal.addEventListener('show.bs.modal', function() {
|
||||
select.innerHTML = '<option value="">Carregando clientes...</option>';
|
||||
|
||||
fetch("{{ route('clientes.lista') }}")
|
||||
@@ -118,4 +235,26 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@section('styles')
|
||||
<style>
|
||||
.table-responsive-dropdown {
|
||||
overflow-x: auto;
|
||||
overflow-y: visible !important;
|
||||
}
|
||||
|
||||
.card.overflow-visible,
|
||||
.card-body.overflow-visible {
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.td-options {
|
||||
position: relative;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.td-options .dropdown-menu {
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
1825
resources/views/reports/conditional-access.blade.php
Normal file
1825
resources/views/reports/conditional-access.blade.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,11 +10,11 @@
|
||||
<link href="{{ asset('assets/css/plugins.css') }}" rel="stylesheet">
|
||||
<link href="{{ asset('assets/css/style.css') }}" rel="stylesheet">
|
||||
<!-- Plugins Stylesheet -->
|
||||
<link href="/assets/vendor/metismenu/dist/metisMenu.min.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/bootstrap-select/dist/css/bootstrap-select.min.css" rel="stylesheet">
|
||||
<link class="main-switcher" href="/assets/css/switcher.css" rel="stylesheet">
|
||||
<link href="{{ asset('assets/vendor/metismenu/dist/metisMenu.min.css') }}" rel="stylesheet">
|
||||
<link href="{{ asset('assets/vendor/bootstrap-select/dist/css/bootstrap-select.min.css') }}" rel="stylesheet">
|
||||
<link class="main-switcher" href="{{ asset('assets/css/switcher.css') }}" rel="stylesheet">
|
||||
|
||||
<link href="assets/vendor/dropzone/dropzone.min.css" rel="stylesheet">
|
||||
<link href="{{ asset('assets/vendor/dropzone/dropzone.min.css') }}" rel="stylesheet">
|
||||
@yield('styles')
|
||||
</head>
|
||||
|
||||
|
||||
@@ -9,4 +9,5 @@ Artisan::command('inspire', function () {
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
Schedule::command('sincronismo-clientes:all')->dailyAt('10:00');
|
||||
Schedule::command('sincronismo-clientes:all')->dailyAt('18:00');
|
||||
Schedule::command('sincronismo-clientes:all')->dailyAt('18:00');
|
||||
Schedule::command('conditional-access:project-shadow --chunk=1000')->hourly();
|
||||
@@ -5,6 +5,7 @@ use App\Http\Controllers\M365CredController;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Http\Controllers\ClientesRelatorioController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\ConditionalAccessReportController;
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +21,8 @@ Route::middleware('auth')->group(function () {
|
||||
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
|
||||
Route::get('/dashboard', function () {
|
||||
return view('dashboard'); })->name('dashboard');
|
||||
return view('dashboard');
|
||||
})->name('dashboard');
|
||||
|
||||
## Clientes
|
||||
Route::get('/clientes', [ClientesController::class, 'index'])->name('clientes');
|
||||
@@ -43,6 +45,17 @@ Route::middleware('auth')->group(function () {
|
||||
## Relatórios
|
||||
Route::get('/relatorios', [ClientesRelatorioController::class, 'index'])->name('relatorios');
|
||||
Route::post('/relatorios/store', [ClientesRelatorioController::class, 'store'])->name('relatorios.store');
|
||||
|
||||
Route::get('/relatorios/acesso-condicional', [ConditionalAccessReportController::class, 'index'])
|
||||
->name('reports.conditional-access');
|
||||
|
||||
Route::get('/relatorios/acesso-condicional/summary', [ConditionalAccessReportController::class, 'summary'])
|
||||
->name('reports.conditional-access.summary');
|
||||
|
||||
Route::get('/relatorios/acesso-condicional/data', [ConditionalAccessReportController::class, 'data'])
|
||||
->name('reports.conditional-access.data');
|
||||
Route::get('/relatorios/acesso-condicional/export', [ConditionalAccessReportController::class, 'export'])
|
||||
->name('reports.conditional-access.export');
|
||||
});
|
||||
|
||||
require __DIR__ . '/auth.php';
|
||||
|
||||
Reference in New Issue
Block a user