lnbits-legend/lnbits/static/js/audit.js
2024-11-27 13:06:35 +02:00

386 lines
10 KiB
JavaScript

window.app = Vue.createApp({
el: '#vue',
mixins: [window.windowMixin],
data: function () {
return {
auditEntries: [],
searchData: {
user_id: '',
ip_address: '',
request_type: '',
component: '',
request_method: '',
response_code: '',
path: ''
},
searchOptions: {
component: [],
request_method: [],
response_code: []
},
auditTable: {
columns: [
{
name: 'created_at',
align: 'center',
label: 'Date',
field: 'created_at',
sortable: true
},
{
name: 'duration',
align: 'left',
label: 'Duration (sec)',
field: 'duration',
sortable: true
},
{
name: 'component',
align: 'left',
label: 'Component',
field: 'component',
sortable: false
},
{
name: 'request_method',
align: 'left',
label: 'Method',
field: 'request_method',
sortable: false
},
{
name: 'response_code',
align: 'left',
label: 'Code',
field: 'response_code',
sortable: false
},
{
name: 'user_id',
align: 'left',
label: 'User Id',
field: 'user_id',
sortable: false
},
{
name: 'ip_address',
align: 'left',
label: 'IP Address',
field: 'ip_address',
sortable: false
},
{
name: 'path',
align: 'left',
label: 'Path',
field: 'path',
sortable: false
}
],
pagination: {
sortBy: 'created_at',
rowsPerPage: 10,
page: 1,
descending: true,
rowsNumber: 10
},
search: null,
hideEmpty: true,
loading: false
},
auditDetailsDialog: {
data: null,
show: false
}
}
},
async created() {
await this.fetchAudit()
},
mounted() {
this.initCharts()
},
methods: {
async fetchAudit(props) {
try {
const params = LNbits.utils.prepareFilterQuery(this.auditTable, props)
const {data} = await LNbits.api.request(
'GET',
`/audit/api/v1?${params}`
)
this.auditTable.pagination.rowsNumber = data.total
this.auditEntries = data.data
await this.fetchAuditStats(props)
} catch (error) {
console.warn(error)
LNbits.utils.notifyApiError(error)
} finally {
this.auditTable.loading = false
}
},
async fetchAuditStats(props) {
try {
const params = LNbits.utils.prepareFilterQuery(this.auditTable, props)
const {data} = await LNbits.api.request(
'GET',
`/audit/api/v1/stats?${params}`
)
const request_methods = data.request_method.map(rm => rm.field)
this.searchOptions.request_method = [
...new Set(this.searchOptions.request_method.concat(request_methods))
]
this.requestMethodChart.data.labels = request_methods
this.requestMethodChart.data.datasets[0].data = data.request_method.map(
rm => rm.total
)
this.requestMethodChart.update()
const response_codes = data.response_code.map(rm => rm.field)
this.searchOptions.response_code = [
...new Set(this.searchOptions.response_code.concat(response_codes))
]
this.responseCodeChart.data.labels = response_codes
this.responseCodeChart.data.datasets[0].data = data.response_code.map(
rm => rm.total
)
this.responseCodeChart.update()
const components = data.component.map(rm => rm.field)
this.searchOptions.component = [
...new Set(this.searchOptions.component.concat(components))
]
this.componentUseChart.data.labels = components
this.componentUseChart.data.datasets[0].data = data.component.map(
rm => rm.total
)
this.componentUseChart.update()
this.longDurationChart.data.labels = data.long_duration.map(
rm => rm.field
)
this.longDurationChart.data.datasets[0].data = data.long_duration.map(
rm => rm.total
)
this.longDurationChart.update()
} catch (error) {
console.warn(error)
LNbits.utils.notifyApiError(error)
}
},
async searchAuditBy(fieldName, fieldValue) {
if (fieldName) {
this.searchData[fieldName] = fieldValue
}
// remove empty fields
this.auditTable.filter = Object.entries(this.searchData).reduce(
(a, [k, v]) => (v ? ((a[k] = v), a) : a),
{}
)
await this.fetchAudit()
},
showDetailsDialog(entry) {
const details = JSON.parse(entry?.request_details || '')
try {
if (details.body) {
details.body = JSON.parse(details.body)
}
} catch (e) {
// do nothing
}
this.auditDetailsDialog.data = JSON.stringify(details, null, 4)
this.auditDetailsDialog.show = true
},
formatDate: function (value) {
return Quasar.date.formatDate(new Date(value), 'YYYY-MM-DD HH:mm')
},
shortify(value) {
valueLength = (value || '').length
if (valueLength <= 10) {
return value
}
return `${value.substring(0, 5)}...${value.substring(valueLength - 5, valueLength)}`
},
initCharts() {
this.responseCodeChart = new Chart(
this.$refs.responseCodeChart.getContext('2d'),
{
type: 'doughnut',
options: {
responsive: true,
plugins: {
legend: {
position: 'bottom'
},
title: {
display: false,
text: 'HTTP Response Codes'
}
},
onClick: (_, elements, chart) => {
if (elements[0]) {
const i = elements[0].index
this.searchAuditBy('response_code', chart.data.labels[i])
}
}
},
data: {
datasets: [
{
label: '',
data: [20, 10],
backgroundColor: [
'rgb(100, 99, 200)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(255, 5, 86)',
'rgb(25, 205, 86)',
'rgb(255, 205, 250)'
]
}
],
labels: []
}
}
)
this.requestMethodChart = new Chart(
this.$refs.requestMethodChart.getContext('2d'),
{
type: 'bar',
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: false
}
},
onClick: (_, elements, chart) => {
if (elements[0]) {
const i = elements[0].index
this.searchAuditBy('request_method', chart.data.labels[i])
}
}
},
data: {
datasets: [
{
label: '',
data: [],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(255, 5, 86)',
'rgb(25, 205, 86)',
'rgb(255, 205, 250)'
],
hoverOffset: 4
}
]
}
}
)
this.componentUseChart = new Chart(
this.$refs.componentUseChart.getContext('2d'),
{
type: 'pie',
options: {
responsive: true,
plugins: {
legend: {
position: 'xxx'
},
title: {
display: false,
text: 'Components'
}
},
onClick: (_, elements, chart) => {
if (elements[0]) {
const i = elements[0].index
this.searchAuditBy('component', chart.data.labels[i])
}
}
},
data: {
datasets: [
{
data: [],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(255, 5, 86)',
'rgb(25, 205, 86)',
'rgb(255, 205, 250)',
'rgb(100, 205, 250)',
'rgb(120, 205, 250)',
'rgb(140, 205, 250)',
'rgb(160, 205, 250)'
],
hoverOffset: 4
}
]
}
}
)
this.longDurationChart = new Chart(
this.$refs.longDurationChart.getContext('2d'),
{
type: 'bar',
options: {
responsive: true,
indexAxis: 'y',
maintainAspectRatio: false,
plugins: {
legend: {
title: {
display: false,
text: 'Long Duration'
}
}
},
onClick: (_, elements, chart) => {
if (elements[0]) {
const i = elements[0].index
this.searchAuditBy('path', chart.data.labels[i])
}
}
},
data: {
datasets: [
{
label: '',
data: [],
backgroundColor: [
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)',
'rgb(255, 5, 86)',
'rgb(25, 205, 86)',
'rgb(255, 205, 250)',
'rgb(100, 205, 250)',
'rgb(120, 205, 250)',
'rgb(140, 205, 250)',
'rgb(160, 205, 250)'
],
hoverOffset: 4
}
]
}
}
)
}
}
})