Files
BarangaySystem/resources/js/Pages/AdminConsole.vue
Jonathan Sykes fbb7e3ff37
Some checks failed
tests / PHP 8.2 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.3 (swoole-5.1.6) (push) Has been cancelled
tests / PHP 8.4 (swoole-6.0) (push) Has been cancelled
feat: implement barangay system phases 2-14
Complete adaptation from BukidBountyApp to Philippine barangay governance:

- Barangay models: Resident, Household, HouseholdMember, Blotter, BlotterHearing,
  DocumentRequest, RequestPayment, RequestType, BarangayProject, BarangayBudget
- Controllers: ResidentController, HouseholdController, BlotterController,
  BlotterHearingController, DocumentRequestController, RequestTypeController,
  ProjectController, BudgetController, QRPHController, AdminConsoleController,
  UserController, FileController, ChapterController, LoginController
- Vue pages: Home, ManageResidents, ResidentProfile, ManageHouseholds, ManageBlotters,
  BlotterDetail, RequestDocument, ManageDocumentRequests, DocumentRequestDetail,
  ManageRequestTypes, ManageProjects, BudgetLedger, AdminConsole
- Barangay roles: PunongBarangay, Kagawad, Secretary, Treasurer, SK, Tanod, BHW, Staff, Resident
- UserPermissions matrix rewritten with barangay-specific permission mappings
- VueRouteMap replaced with barangay SPA routes
- UserActions enum references corrected across all controllers
- Removed all market/cooperative/POS/subscription code and models
2026-06-07 03:09:09 +08:00

195 lines
8.2 KiB
Vue

<script setup>
import { ref, onMounted } from 'vue';
import { usePageTitle } from '../composables/Core/usePageTitle';
import { executeRequest } from '../utils/executeRequest.js';
usePageTitle('Admin Console');
const stats = ref({});
const logs = ref([]);
const queryResult = ref(null);
const queryError = ref('');
const activeTab = ref('stats');
const loading = ref(false);
const sqlQuery = ref('');
const maintenanceMode = ref(false);
const globalMessage = ref('');
const tabs = [
{ key: 'stats', label: 'Statistics' },
{ key: 'logs', label: 'Activity Logs' },
{ key: 'query', label: 'SQL Query' },
{ key: 'maintenance', label: 'Maintenance' },
];
const loadStats = async () => {
const res = await executeRequest('/admin/console/stats');
if (res.success) stats.value = res.data;
};
const loadLogs = async () => {
loading.value = true;
const res = await executeRequest('/admin/console/logs');
if (res.success) logs.value = res.data;
loading.value = false;
};
const runQuery = async () => {
if (!sqlQuery.value.trim()) return;
queryError.value = '';
queryResult.value = null;
const res = await executeRequest('/admin/console/query', 'POST', { query: sqlQuery.value });
if (res.success) {
queryResult.value = res.data;
} else {
queryError.value = res.message ?? 'Query failed.';
}
};
const clearCache = async () => {
if (!confirm('Clear all application cache?')) return;
const res = await executeRequest('/admin/console/cache/clear', 'POST');
alert(res.message ?? 'Done.');
};
const setMaintenance = async (val) => {
await executeRequest('/admin/console/maintenance', 'POST', { enabled: val });
maintenanceMode.value = val;
};
const setGlobalMessage = async () => {
const res = await executeRequest('/admin/console/message', 'POST', { message: globalMessage.value });
if (res.success) alert('Global message updated.');
};
const queryColumns = () => {
if (!queryResult.value || !queryResult.value.length) return [];
return Object.keys(queryResult.value[0]);
};
onMounted(() => {
loadStats();
});
const onTabChange = (tab) => {
activeTab.value = tab;
if (tab === 'logs') loadLogs();
};
</script>
<template>
<div class="p-4 max-w-6xl mx-auto">
<h1 class="text-2xl font-bold mb-1">Admin Console</h1>
<p class="text-gray-500 text-sm mb-4">System administration and diagnostics</p>
<!-- Tabs -->
<div class="flex border-b mb-5">
<button
v-for="tab in tabs" :key="tab.key"
@click="onTabChange(tab.key)"
:class="`px-4 py-2 text-sm font-medium border-b-2 -mb-px transition ${activeTab === tab.key ? 'border-blue-500 text-blue-600' : 'border-transparent text-gray-500 hover:text-gray-700'}`">
{{ tab.label }}
</button>
</div>
<!-- Statistics -->
<div v-if="activeTab === 'stats'">
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3 mb-6">
<div v-for="(val, key) in stats" :key="key" class="bg-white rounded-lg shadow p-4 text-center">
<div class="text-3xl font-bold text-blue-600">{{ val }}</div>
<div class="text-xs text-gray-500 mt-1 capitalize">{{ key.replace(/_/g, ' ') }}</div>
</div>
</div>
<button @click="loadStats" class="btn-secondary text-sm">Refresh</button>
</div>
<!-- Logs -->
<div v-else-if="activeTab === 'logs'">
<div v-if="loading" class="text-center py-8 text-gray-400">Loading...</div>
<div v-else class="bg-white rounded-lg shadow overflow-hidden">
<table class="w-full text-sm">
<thead class="bg-gray-50 text-gray-600 text-left">
<tr>
<th class="px-4 py-2">Time</th>
<th class="px-4 py-2">User</th>
<th class="px-4 py-2">Action</th>
<th class="px-4 py-2">Details</th>
</tr>
</thead>
<tbody>
<tr v-for="log in logs" :key="log.id" class="border-t hover:bg-gray-50">
<td class="px-4 py-2 text-gray-400 whitespace-nowrap">{{ log.created_at }}</td>
<td class="px-4 py-2">{{ log.user?.username ?? log.user_id }}</td>
<td class="px-4 py-2 font-mono text-xs">{{ log.action }}</td>
<td class="px-4 py-2 text-gray-500 max-w-xs truncate">{{ log.details }}</td>
</tr>
</tbody>
</table>
<p v-if="!logs.length" class="text-center text-gray-400 py-6">No logs found.</p>
</div>
</div>
<!-- SQL Query -->
<div v-else-if="activeTab === 'query'">
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4 text-sm text-yellow-800">
Only SELECT queries are permitted. This console is for diagnostics only.
</div>
<textarea
v-model="sqlQuery"
class="input font-mono text-sm h-32 mb-3"
placeholder="SELECT * FROM barangay_residents LIMIT 10;"
></textarea>
<button @click="runQuery" class="btn-primary mb-4">Run Query</button>
<div v-if="queryError" class="bg-red-50 border border-red-200 rounded p-3 text-red-700 text-sm mb-3">
{{ queryError }}
</div>
<div v-if="queryResult" class="bg-white rounded-lg shadow overflow-x-auto">
<div class="text-xs text-gray-400 px-4 pt-3">{{ queryResult.length }} row(s)</div>
<table class="w-full text-xs">
<thead class="bg-gray-50">
<tr>
<th v-for="col in queryColumns()" :key="col" class="px-3 py-2 text-left text-gray-600 font-medium">{{ col }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, i) in queryResult" :key="i" class="border-t">
<td v-for="col in queryColumns()" :key="col" class="px-3 py-1.5 text-gray-700 max-w-xs truncate">
{{ row[col] ?? '' }}
</td>
</tr>
</tbody>
</table>
<p v-if="!queryResult.length" class="text-center text-gray-400 py-4 text-sm">No rows returned.</p>
</div>
</div>
<!-- Maintenance -->
<div v-else-if="activeTab === 'maintenance'" class="space-y-5">
<div class="bg-white rounded-lg shadow p-5">
<h3 class="font-semibold text-gray-700 mb-3">Maintenance Mode</h3>
<p class="text-sm text-gray-500 mb-3">Enabling maintenance mode will show a maintenance page to non-admin users.</p>
<div class="flex gap-2">
<button @click="setMaintenance(true)" class="btn-sm bg-red-500 text-white">Enable Maintenance</button>
<button @click="setMaintenance(false)" class="btn-sm bg-green-500 text-white">Disable Maintenance</button>
</div>
</div>
<div class="bg-white rounded-lg shadow p-5">
<h3 class="font-semibold text-gray-700 mb-3">Global Message</h3>
<p class="text-sm text-gray-500 mb-3">Show a banner message to all users across the system.</p>
<textarea v-model="globalMessage" class="input h-20 mb-3" placeholder="System maintenance scheduled for..."></textarea>
<button @click="setGlobalMessage" class="btn-secondary">Set Global Message</button>
</div>
<div class="bg-white rounded-lg shadow p-5">
<h3 class="font-semibold text-gray-700 mb-3">Cache Management</h3>
<p class="text-sm text-gray-500 mb-3">Clear Redis cache and application cached data.</p>
<button @click="clearCache" class="btn-sm bg-orange-500 text-white">Clear All Cache</button>
</div>
</div>
</div>
</template>