Updated Logging System to Include Application Logs
This commit is contained in:
@@ -540,6 +540,7 @@ export default {
|
||||
'admin-users': 'User accounts, teams, and role permissions',
|
||||
'admin-security': 'Password rules, account lockout, and complexity policy',
|
||||
'admin-audit': 'Complete audit trail of all user activity, with export',
|
||||
'admin-logs': 'Application/event log — HTTP, SMTP, webhook, and Teams activity',
|
||||
'admin-config': 'Application settings, maintenance defaults, and tax rule sets',
|
||||
'admin-integrations': 'Email, webhooks, ServiceNow, and Workday connectors'
|
||||
}[this.activeView];
|
||||
@@ -563,6 +564,7 @@ export default {
|
||||
'admin-users': 'Users & Teams',
|
||||
'admin-security': 'Password & Security',
|
||||
'admin-audit': 'Activity & Audit Trail',
|
||||
'admin-logs': 'Application Logs',
|
||||
'admin-config': 'Application Configuration',
|
||||
'admin-integrations': 'Integrations'
|
||||
}[this.activeView];
|
||||
@@ -627,6 +629,7 @@ export default {
|
||||
'admin-users': ['admin.users'],
|
||||
'admin-security': ['admin.security'],
|
||||
'admin-audit': ['admin.audit'],
|
||||
'admin-logs': ['admin.logs'],
|
||||
'admin-config': ['admin.settings', 'config.manage'],
|
||||
'admin-integrations': ['admin.integrations']
|
||||
};
|
||||
|
||||
151
src/components/AppLogsSettings.vue
Normal file
151
src/components/AppLogsSettings.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<v-card class="span-12 panel-pad">
|
||||
<div class="toolbar-row mb-3">
|
||||
<div>
|
||||
<h2 class="section-title">Application logs</h2>
|
||||
<p class="quiet text-body-2">
|
||||
Operational/event log of the application — HTTP requests and outbound integration activity (SMTP, webhook, Teams,
|
||||
ServiceNow, Workday). Separate from <strong>Activity & Audit Trail</strong>, which records user data changes.
|
||||
</p>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-btn variant="tonal" prepend-icon="mdi-refresh" :loading="loading" @click="load">Refresh</v-btn>
|
||||
</div>
|
||||
|
||||
<div class="filter-grid mb-3">
|
||||
<v-select v-model="filters.category" :items="categories" label="Category" density="compact" hide-details clearable @update:model-value="load" />
|
||||
<v-select v-model="filters.level" :items="levels" label="Level" density="compact" hide-details clearable @update:model-value="load" />
|
||||
<v-text-field
|
||||
v-model="filters.q"
|
||||
label="Search"
|
||||
placeholder="message or detail…"
|
||||
density="compact"
|
||||
hide-details
|
||||
clearable
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
@keyup.enter="load"
|
||||
@click:clear="load"
|
||||
/>
|
||||
</div>
|
||||
<div class="toolbar-row mb-3">
|
||||
<v-btn size="small" color="primary" variant="tonal" prepend-icon="mdi-filter" @click="load">Apply</v-btn>
|
||||
<v-spacer />
|
||||
<span class="quiet text-body-2">{{ total }} matching event{{ total === 1 ? '' : 's' }} (showing {{ logs.length }})</span>
|
||||
</div>
|
||||
|
||||
<v-alert v-if="error" type="error" density="compact" class="mb-3">{{ error }}</v-alert>
|
||||
|
||||
<v-table density="compact" class="log-table">
|
||||
<thead>
|
||||
<tr><th>Time</th><th>Level</th><th>Category</th><th>Message</th><th>Detail</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, i) in logs" :key="i">
|
||||
<td class="nowrap">{{ shortDate(row.timestamp) }}</td>
|
||||
<td><v-chip size="x-small" :color="levelColor(row.level)" variant="tonal">{{ row.level }}</v-chip></td>
|
||||
<td><v-chip size="x-small" variant="tonal">{{ row.category }}</v-chip></td>
|
||||
<td>{{ row.message }}</td>
|
||||
<td>
|
||||
<v-menu v-if="row.meta && Object.keys(row.meta).length" location="start">
|
||||
<template #activator="{ props }">
|
||||
<v-btn v-bind="props" size="x-small" variant="text" icon="mdi-information-outline" />
|
||||
</template>
|
||||
<v-card class="detail-card pa-3">
|
||||
<pre class="detail-json">{{ pretty(row.meta) }}</pre>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
<span v-else class="quiet">—</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="!logs.length && !loading">
|
||||
<td colspan="5" class="text-center quiet py-4">No log events match these filters.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { apiRequest } from '../services/api';
|
||||
import { shortDate } from '../utils/format';
|
||||
|
||||
// Application Logs viewer (Admin → Application Logs). Reads the Winston event log file via
|
||||
// /api/app-logs (filtered by category/level/search); the category/level dropdowns come from the facets
|
||||
// the endpoint returns. Distinct from the DB-backed Activity & Audit Trail.
|
||||
export default {
|
||||
props: {
|
||||
token: { type: String, required: true }
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filters: { category: null, level: null, q: '' },
|
||||
logs: [],
|
||||
categories: [],
|
||||
levels: [],
|
||||
total: 0,
|
||||
loading: false,
|
||||
error: ''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.load();
|
||||
},
|
||||
methods: {
|
||||
shortDate,
|
||||
async api(path, options = {}) {
|
||||
return apiRequest(path, this.token, options);
|
||||
},
|
||||
async load() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
try {
|
||||
const params = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(this.filters)) {
|
||||
if (value !== null && value !== '' && value !== undefined) params.set(key, value);
|
||||
}
|
||||
const data = await (await this.api(`/api/app-logs?${params.toString()}`)).json();
|
||||
this.logs = data.logs;
|
||||
this.total = data.total;
|
||||
this.categories = data.categories;
|
||||
this.levels = data.levels;
|
||||
} catch (error) {
|
||||
this.error = error.message;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
levelColor(level) {
|
||||
return { error: 'error', warn: 'warning', info: 'info', debug: 'grey' }[level] || undefined;
|
||||
},
|
||||
pretty(meta) {
|
||||
try {
|
||||
return JSON.stringify(meta, null, 2);
|
||||
} catch {
|
||||
return String(meta);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.filter-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.log-table .nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.detail-card {
|
||||
max-width: 520px;
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.detail-json {
|
||||
font-size: 11px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -173,6 +173,7 @@ export const navItems = [
|
||||
{ key: 'admin-users', label: 'Users & Teams', icon: 'mdi-account-group-outline' },
|
||||
{ key: 'admin-security', label: 'Password & Security', icon: 'mdi-shield-key-outline' },
|
||||
{ key: 'admin-audit', label: 'Activity & Audit Trail', icon: 'mdi-history' },
|
||||
{ key: 'admin-logs', label: 'Application Logs', icon: 'mdi-text-box-search-outline' },
|
||||
{ key: 'admin-config', label: 'Application Configuration', icon: 'mdi-tune' },
|
||||
{ key: 'admin-integrations', label: 'Integrations', icon: 'mdi-transit-connection-variant' }
|
||||
]
|
||||
|
||||
@@ -170,6 +170,11 @@
|
||||
<AuditTrailSettings :token="token" />
|
||||
</template>
|
||||
|
||||
<!-- APPLICATION LOGS -->
|
||||
<template v-if="adminSection === 'admin-logs'">
|
||||
<AppLogsSettings :token="token" />
|
||||
</template>
|
||||
|
||||
<!-- APPLICATION CONFIGURATION -->
|
||||
<template v-if="adminSection === 'admin-config'">
|
||||
<CompanySettings :token="token" @updated="$emit('companies-updated')" />
|
||||
@@ -309,6 +314,7 @@
|
||||
<script>
|
||||
import AssetClassSettings from '../components/AssetClassSettings.vue';
|
||||
import AssetIdTemplateSettings from '../components/AssetIdTemplateSettings.vue';
|
||||
import AppLogsSettings from '../components/AppLogsSettings.vue';
|
||||
import AuditTrailSettings from '../components/AuditTrailSettings.vue';
|
||||
import CategorySettings from '../components/CategorySettings.vue';
|
||||
import CompanySettings from '../components/CompanySettings.vue';
|
||||
@@ -326,7 +332,7 @@ import TeamsSettings from '../components/TeamsSettings.vue';
|
||||
import WebhookSettings from '../components/WebhookSettings.vue';
|
||||
|
||||
export default {
|
||||
components: { AssetClassSettings, AssetIdTemplateSettings, AuditTrailSettings, CategorySettings, CompanySettings, DataTable, DepartmentSettings, DepreciationZoneSettings, NotificationSettings, PartCategorySettings, PasswordPolicySettings, PmCategorySettings, PmSettings, Section179LimitSettings, ServiceNowSettings, TeamsSettings, WebhookSettings },
|
||||
components: { AppLogsSettings, AssetClassSettings, AssetIdTemplateSettings, AuditTrailSettings, CategorySettings, CompanySettings, DataTable, DepartmentSettings, DepreciationZoneSettings, NotificationSettings, PartCategorySettings, PasswordPolicySettings, PmCategorySettings, PmSettings, Section179LimitSettings, ServiceNowSettings, TeamsSettings, WebhookSettings },
|
||||
props: {
|
||||
token: { type: String, default: '' },
|
||||
section: { type: String, default: 'admin-users' },
|
||||
@@ -397,7 +403,7 @@ export default {
|
||||
return groups;
|
||||
},
|
||||
adminSection() {
|
||||
return ['admin-users', 'admin-security', 'admin-audit', 'admin-config', 'admin-integrations'].includes(this.section) ? this.section : 'admin-users';
|
||||
return ['admin-users', 'admin-security', 'admin-audit', 'admin-logs', 'admin-config', 'admin-integrations'].includes(this.section) ? this.section : 'admin-users';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
Reference in New Issue
Block a user