better view of event logs
This commit is contained in:
@@ -136,7 +136,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {defineEmits, ref} from 'vue'
|
import {ref} from 'vue'
|
||||||
import {useStorage} from '@vueuse/core'
|
import {useStorage} from '@vueuse/core'
|
||||||
import {notification} from '~/utils/index'
|
import {notification} from '~/utils/index'
|
||||||
import awaiter from '~/utils/awaiter'
|
import awaiter from '~/utils/awaiter'
|
||||||
|
|||||||
235
frontend/components/EventView.vue
Normal file
235
frontend/components/EventView.vue
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
<div class="column is-12 is-clearfix is-unselectable">
|
||||||
|
<div class="is-pulled-right">
|
||||||
|
<div class="field is-grouped">
|
||||||
|
<div class="control has-icons-left" v-if="toggleFilter">
|
||||||
|
<input type="search" v-model.lazy="query" class="input" id="filter" placeholder="Filter">
|
||||||
|
<span class="icon is-left"><i class="fas fa-filter" /></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control">
|
||||||
|
<button class="button is-danger is-light" @click="toggleFilter = !toggleFilter"
|
||||||
|
:disabled="!item?.logs || item.logs.length < 1" v-tooltip.bottom="'Filter event logs.'">
|
||||||
|
<span class="icon"><i class="fas fa-filter" /></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="control">
|
||||||
|
<button class="button is-warning" @click="resetEvent(0 === item.status ? 4 : 0)"
|
||||||
|
:disabled="1 === item.status" v-tooltip.bottom="'Reset event.'">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas"
|
||||||
|
:class="{ 'fa-trash-arrow-up': 0 !== item.status, 'fa-power-off': 0 === item.status }"></i>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<button class="button is-danger" @click="deleteItem" :disabled="1 === item.status"
|
||||||
|
v-tooltip.bottom="'Delete event.'">
|
||||||
|
<span class="icon"><i class="fas fa-trash" /></span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<button class="button is-info" @click="loadContent()" :class="{ 'is-loading': isLoading }"
|
||||||
|
:disabled="isLoading" v-tooltip.bottom="'Reload event data.'">
|
||||||
|
<span class="icon"><i class="fas fa-sync" /></span>
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="is-hidden-mobile">
|
||||||
|
<span class="subtitle"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" v-if="isLoading">
|
||||||
|
<Message v-if="isLoading" message_class="has-background-info-90 has-text-dark" title="Loading"
|
||||||
|
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..." />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!isLoading" class="columns is-multiline">
|
||||||
|
<div class="column is-12">
|
||||||
|
<div class="notification">
|
||||||
|
<p class="title is-5">
|
||||||
|
Event <span class="tag is-info">{{ item.event }}</span>
|
||||||
|
<template v-if="item.reference">
|
||||||
|
with reference <span class="tag is-info is-light">{{ item.reference }}</span>
|
||||||
|
</template>
|
||||||
|
was created
|
||||||
|
<span class="tag is-warning">
|
||||||
|
<time class="has-tooltip" v-tooltip="moment(item.created_at).format(TOOLTIP_DATE_FORMAT)">
|
||||||
|
{{ moment(item.created_at).fromNow() }}
|
||||||
|
</time>
|
||||||
|
</span>, and last updated
|
||||||
|
<span class="tag is-danger">
|
||||||
|
<span v-if="!item.updated_at">not started</span>
|
||||||
|
<time v-else class="has-tooltip"
|
||||||
|
v-tooltip="moment(item.updated_at).format(TOOLTIP_DATE_FORMAT)">
|
||||||
|
{{ moment(item.updated_at).fromNow() }}
|
||||||
|
</time>
|
||||||
|
</span>,
|
||||||
|
with status of <span class="tag" :class="getStatusClass(item.status)">{{ item.status }}:
|
||||||
|
{{ item.status_name }}</span>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" v-if="item?.event_data && Object.keys(item.event_data).length > 0">
|
||||||
|
<h2 class="title is-4 is-clickable is-unselectable" @click="toggleData = !toggleData">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas" :class="{ 'fa-arrow-down': !toggleData, 'fa-arrow-up': toggleData }"></i>
|
||||||
|
</span>
|
||||||
|
<span>Show attached data</span>
|
||||||
|
</h2>
|
||||||
|
<div v-if="toggleData" class="is-relative">
|
||||||
|
<code style="word-break: break-word" class="is-pre-wrap is-block">
|
||||||
|
{{ JSON.stringify(item.event_data, null, 2) }}
|
||||||
|
</code>
|
||||||
|
<button class="button m-4" v-tooltip="'Copy event data'"
|
||||||
|
@click="() => copyText(JSON.stringify(item.event_data, null, 2))"
|
||||||
|
style="position: absolute; top:0; right:0;">
|
||||||
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" v-if="item?.logs && item.logs.length > 0">
|
||||||
|
<h2 class="title is-4 is-clickable is-unselectable" @click="toggleLogs = !toggleLogs">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas" :class="{ 'fa-arrow-down': !toggleLogs, 'fa-arrow-up': toggleLogs }"></i>
|
||||||
|
</span>
|
||||||
|
<span>Show event logs</span>
|
||||||
|
</h2>
|
||||||
|
<div v-if="toggleLogs" class="is-relative">
|
||||||
|
<code style="word-break: break-word" class="is-pre-wrap is-block">
|
||||||
|
{{ JSON.stringify(filteredRows, null, 2) }}
|
||||||
|
</code>
|
||||||
|
<button class="button m-4" v-tooltip="'Copy logs'"
|
||||||
|
@click="() => copyText(JSON.stringify(filteredRows, null, 2))"
|
||||||
|
style="position: absolute; top:0; right:0;">
|
||||||
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column is-12" v-if="item?.options">
|
||||||
|
<h2 class="title is-4 is-clickable is-unselectable" @click="toggleOptions = !toggleOptions">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas" :class="{ 'fa-arrow-down': !toggleOptions, 'fa-arrow-up': toggleOptions }"></i>
|
||||||
|
</span>
|
||||||
|
<span>Show attached options</span>
|
||||||
|
</h2>
|
||||||
|
<div v-if="toggleOptions" class="is-relative">
|
||||||
|
<code style="word-break: break-word" class="is-pre-wrap is-block">
|
||||||
|
{{ JSON.stringify(item.options, null, 2) }}
|
||||||
|
</code>
|
||||||
|
<button class="button m-4" v-tooltip="'Copy options'"
|
||||||
|
@click="() => copyText(JSON.stringify(item.options, null, 2))"
|
||||||
|
style="position: absolute; top:0; right:0;">
|
||||||
|
<span class="icon"><i class="fas fa-copy"></i></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { copyText, notification, parse_api_response, TOOLTIP_DATE_FORMAT } from '~/utils/index'
|
||||||
|
import request from '~/utils/request'
|
||||||
|
import moment from 'moment'
|
||||||
|
import { getStatusClass, makeName } from '~/utils/events/helpers'
|
||||||
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
|
const emitter = defineEmits(['closeOverlay', 'deleted'])
|
||||||
|
const props = defineProps({ id: { type: Number, required: true } })
|
||||||
|
|
||||||
|
const query = ref()
|
||||||
|
const item = ref({})
|
||||||
|
const isLoading = ref(true)
|
||||||
|
const toggleFilter = ref(false)
|
||||||
|
|
||||||
|
const toggleLogs = useStorage('events_toggle_logs', true)
|
||||||
|
const toggleData = useStorage('events_toggle_data', true)
|
||||||
|
const toggleOptions = useStorage('events_toggle_options', true)
|
||||||
|
|
||||||
|
watch(toggleFilter, () => {
|
||||||
|
if (!toggleFilter.value) {
|
||||||
|
query.value = ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filteredRows = computed(() => {
|
||||||
|
if (!query.value) {
|
||||||
|
return item.value.logs ?? []
|
||||||
|
}
|
||||||
|
return item.value.logs.filter(m => m.toLowerCase().includes(query.value.toLowerCase()));
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
if (!props.id) {
|
||||||
|
throw createError({
|
||||||
|
statusCode: 404,
|
||||||
|
message: 'Error ID not provided.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return await loadContent()
|
||||||
|
})
|
||||||
|
|
||||||
|
const loadContent = async () => {
|
||||||
|
try {
|
||||||
|
isLoading.value = true
|
||||||
|
const response = await request(`/system/events/${props.id}`,)
|
||||||
|
const json = await parse_api_response(response)
|
||||||
|
|
||||||
|
if (200 !== response.status) {
|
||||||
|
notification('error', 'Error', `Errors viewItem request error. ${json.error.code}: ${json.error.message}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.value = json
|
||||||
|
|
||||||
|
useHead({ title: `Event: ${json.id}` })
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
notification('crit', 'Error', `Errors viewItem Request failure. ${e.message}`
|
||||||
|
)
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteItem = async () => emitter('delete', item.value)
|
||||||
|
|
||||||
|
const resetEvent = async (status = 0) => {
|
||||||
|
if (!confirm(`Reset '${makeName(item.value.id)}'?`)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await request(`/system/events/${item.value.id}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify({
|
||||||
|
status: status,
|
||||||
|
reset_logs: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const json = await parse_api_response(response)
|
||||||
|
|
||||||
|
if (200 !== response.status) {
|
||||||
|
notification('error', 'Error', `Events view patch Request error. ${json.error.code}: ${json.error.message}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item.value = json
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
notification('crit', 'Error', `Events view patch Request failure. ${e.message}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="modal is-active">
|
<div class="modal is-active">
|
||||||
<div class="modal-background"></div>
|
<div class="modal-background" @click="closeOverLay()"></div>
|
||||||
<div class="modal-card" style="min-width: calc(100% - 30%);">
|
<div class="modal-card" style="min-width: calc(100% - 30%);">
|
||||||
<header class="modal-card-head">
|
<header class="modal-card-head">
|
||||||
<p class="modal-card-title" v-text="model_title"></p>
|
<p class="modal-card-title" v-text="model_title"></p>
|
||||||
<button class="delete" @click="closeOverLay"></button>
|
<button class="delete" @click="closeOverLay"></button>
|
||||||
</header>
|
</header>
|
||||||
<div class="modal-card-body">
|
<div class="modal-card-body">
|
||||||
<slot/>
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,4 +26,14 @@ const props = defineProps({
|
|||||||
const model_title = ref(props.title.replace(/^\//g, ''));
|
const model_title = ref(props.title.replace(/^\//g, ''));
|
||||||
|
|
||||||
const closeOverLay = () => emit('closeOverlay');
|
const closeOverLay = () => emit('closeOverlay');
|
||||||
|
|
||||||
|
const eventHandler = e => {
|
||||||
|
if (e.key !== 'Escape') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
closeOverLay()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => window.addEventListener('keydown', eventHandler))
|
||||||
|
onUnmounted(() => window.removeEventListener('keydown', eventHandler))
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,27 +8,27 @@
|
|||||||
</span>
|
</span>
|
||||||
<div class="is-pulled-right">
|
<div class="is-pulled-right">
|
||||||
<div class="field is-grouped">
|
<div class="field is-grouped">
|
||||||
<div class="control has-icons-left" v-if="toggleFilter">
|
<div class="control has-icons-left" v-if="toggleFilter || query">
|
||||||
<input type="search" v-model.lazy="query" class="input" id="filter" placeholder="Filter">
|
<input type="search" v-model.lazy="query" class="input" id="filter" placeholder="Filter">
|
||||||
<span class="icon is-left"><i class="fas fa-filter"/></span>
|
<span class="icon is-left"><i class="fas fa-filter" /></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-danger is-light" @click="toggleFilter = !toggleFilter">
|
<button class="button is-danger is-light" @click="toggleFilter = !toggleFilter">
|
||||||
<span class="icon"><i class="fas fa-filter"/></span>
|
<span class="icon"><i class="fas fa-filter" /></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-danger" @click="deleteAll" v-tooltip.bottom="'Remove All non pending events.'">
|
<button class="button is-danger" @click="deleteAll" v-tooltip.bottom="'Remove All non pending events.'">
|
||||||
<span class="icon"><i class="fas fa-trash"/></span>
|
<span class="icon"><i class="fas fa-trash" /></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="control">
|
<p class="control">
|
||||||
<button class="button is-info" @click="loadContent(page, false)"
|
<button class="button is-info" @click="loadContent(page, false)" :class="{ 'is-loading': isLoading }"
|
||||||
:class="{'is-loading': isLoading}" :disabled="isLoading">
|
:disabled="isLoading">
|
||||||
<span class="icon"><i class="fas fa-sync"/></span>
|
<span class="icon"><i class="fas fa-sync" /></span>
|
||||||
</button>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,16 +41,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="column is-12" v-if="total && last_page > 1">
|
<div class="column is-12" v-if="total && last_page > 1">
|
||||||
<Pager @navigate="ePage => loadContent(ePage)" :last_page="last_page" :page="page" :is-loading="isLoading"/>
|
<Pager @navigate="ePage => loadContent(ePage)" :last_page="last_page" :page="page" :is-loading="isLoading" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="columns is-multiline" v-if="filteredRows.length < 1">
|
<div class="columns is-multiline" v-if="filteredRows.length < 1">
|
||||||
<div class="column is-12">
|
<div class="column is-12">
|
||||||
<Message v-if="isLoading" message_class="has-background-info-90 has-text-dark" title="Loading"
|
<Message v-if="isLoading" message_class="has-background-info-90 has-text-dark" title="Loading"
|
||||||
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..."/>
|
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..." />
|
||||||
<Message v-else class="has-background-warning-80 has-text-dark" title="Warning"
|
<Message v-else class="has-background-warning-80 has-text-dark" title="Warning"
|
||||||
icon="fas fa-exclamation-triangle">
|
icon="fas fa-exclamation-triangle">
|
||||||
<p>No items found.</p>
|
<p>No items found.</p>
|
||||||
<p v-if="query">Search for <strong>{{ query }}</strong> returned no results.</p>
|
<p v-if="query">Search for <strong>{{ query }}</strong> returned no results.</p>
|
||||||
</Message>
|
</Message>
|
||||||
@@ -62,23 +62,21 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<header class="card-header is-align-self-flex-end">
|
<header class="card-header is-align-self-flex-end">
|
||||||
<div class="card-header-title is-block">
|
<div class="card-header-title is-block">
|
||||||
<NuxtLink :to="`/events/view/?id=${item.id}`" v-text="makeName(item.id)"/>
|
<NuxtLink @click="quick_view = item.id" v-text="makeName(item.id)" />
|
||||||
<div class="is-pulled-right is-hidden-tablet">
|
<div class="is-pulled-right is-hidden-tablet">
|
||||||
<span class="tag" :class="getStatusClass(item.status)">{{ statuses[item.status].name }}</span>
|
<span class="tag" :class="getStatusClass(item.status)">{{ statuses[item.status].name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-header-icon" @click="item._display = !item._display"
|
<div class="card-header-icon">
|
||||||
v-if="Object.keys(item.event_data).length > 0">
|
<span class="icon" @click="item._display = !item._display" v-if="Object.keys(item.event_data).length > 0">
|
||||||
<span class="icon">
|
<i class="fas" :class="{ 'fa-arrow-up': item?._display, 'fa-arrow-down': !item?._display }" />
|
||||||
<i class="fas" :class="{'fa-arrow-up': item?._display, 'fa-arrow-down': !item?._display }"></i>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content p-0 m-0" v-if="item._display">
|
<div class="card-content p-0 m-0" v-if="item._display">
|
||||||
<pre class="p-0 is-pre" style="position: relative; max-height:30vh; overflow-y:scroll;"><code
|
<pre class="p-0 is-pre" style="position: relative; max-height:30vh; overflow-y:scroll;"><code>{{
|
||||||
class="language-json">{{
|
JSON.stringify(item.event_data, null, 2)
|
||||||
JSON.stringify(item.event_data, null, 2)
|
}}</code><button class="button is-small m-4"
|
||||||
}}</code><button class="button is-small m-4"
|
|
||||||
@click="() => copyText(JSON.stringify(item.event_data), false)"
|
@click="() => copyText(JSON.stringify(item.event_data), false)"
|
||||||
style="position: absolute; top:0; right:0;">
|
style="position: absolute; top:0; right:0;">
|
||||||
<span class="icon"><i class="fas fa-copy"></i></span></button></pre>
|
<span class="icon"><i class="fas fa-copy"></i></span></button></pre>
|
||||||
@@ -109,7 +107,7 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
<div class="card-footer-item" v-text="item.event"/>
|
<div class="card-footer-item" v-text="item.event" />
|
||||||
<div class="card-footer-item">
|
<div class="card-footer-item">
|
||||||
<button class="button is-warning is-fullwidth" @click="resetEvent(item, 0 === item.status ? 4 : 0)">
|
<button class="button is-warning is-fullwidth" @click="resetEvent(item, 0 === item.status ? 4 : 0)">
|
||||||
<span class="icon"><i class="fas fa-trash-arrow-up"></i></span>
|
<span class="icon"><i class="fas fa-trash-arrow-up"></i></span>
|
||||||
@@ -130,7 +128,7 @@
|
|||||||
<div class="columns is-multiline">
|
<div class="columns is-multiline">
|
||||||
<div class="column is-12">
|
<div class="column is-12">
|
||||||
<Message message_class="has-background-info-90 has-text-dark" :toggle="show_page_tips"
|
<Message message_class="has-background-info-90 has-text-dark" :toggle="show_page_tips"
|
||||||
@toggle="show_page_tips = !show_page_tips" :use-toggle="true" title="Tips" icon="fas fa-info-circle">
|
@toggle="show_page_tips = !show_page_tips" :use-toggle="true" title="Tips" icon="fas fa-info-circle">
|
||||||
<ul>
|
<ul>
|
||||||
<li>Resetting event will return it to the queue to be dispatched again.</li>
|
<li>Resetting event will return it to the queue to be dispatched again.</li>
|
||||||
<li>Stopping event will prevent it from being dispatched.</li>
|
<li>Stopping event will prevent it from being dispatched.</li>
|
||||||
@@ -139,17 +137,23 @@
|
|||||||
</Message>
|
</Message>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template v-if="quick_view">
|
||||||
|
<Overlay @closeOverlay="quick_view = null" :title="`#${makeName(quick_view)}`">
|
||||||
|
<EventView :id="quick_view" @delete="item => deleteItem(item)" />
|
||||||
|
</Overlay>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {copyText, notification, parse_api_response} from '~/utils/index'
|
import { copyText, notification, parse_api_response } from '~/utils/index'
|
||||||
import request from '~/utils/request'
|
import request from '~/utils/request'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import Pager from '~/components/Pager'
|
import Pager from '~/components/Pager'
|
||||||
import {getStatusClass, makeName} from '~/utils/events/helpers'
|
import { getStatusClass, makeName } from '~/utils/events/helpers'
|
||||||
import Message from '~/components/Message'
|
import Message from '~/components/Message'
|
||||||
import {useStorage} from '@vueuse/core'
|
import { useStorage } from '@vueuse/core'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
@@ -162,8 +166,9 @@ const isLoading = ref(false)
|
|||||||
const toggleDispatcher = ref(false)
|
const toggleDispatcher = ref(false)
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const statuses = ref([])
|
const statuses = ref([])
|
||||||
const query = ref()
|
const query = ref(route.query.filter ?? '')
|
||||||
const toggleFilter = ref(false)
|
const toggleFilter = ref(false)
|
||||||
|
const quick_view = ref()
|
||||||
const show_page_tips = useStorage('show_page_tips', true)
|
const show_page_tips = useStorage('show_page_tips', true)
|
||||||
|
|
||||||
watch(toggleFilter, () => {
|
watch(toggleFilter, () => {
|
||||||
@@ -205,6 +210,9 @@ const loadContent = async (pageNumber, updateHistory = true) => {
|
|||||||
let queryParams = new URLSearchParams()
|
let queryParams = new URLSearchParams()
|
||||||
queryParams.append('page', pageNumber)
|
queryParams.append('page', pageNumber)
|
||||||
queryParams.append('perpage', p_perpage)
|
queryParams.append('perpage', p_perpage)
|
||||||
|
if (query.value) {
|
||||||
|
queryParams.append('filter', query.value)
|
||||||
|
}
|
||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
toggleDispatcher.value = false
|
toggleDispatcher.value = false
|
||||||
@@ -220,16 +228,19 @@ const loadContent = async (pageNumber, updateHistory = true) => {
|
|||||||
|
|
||||||
let title = `Events - Page #${pageNumber}`
|
let title = `Events - Page #${pageNumber}`
|
||||||
|
|
||||||
useHead({title})
|
useHead({ title })
|
||||||
|
|
||||||
if (true === Boolean(updateHistory)) {
|
if (true === Boolean(updateHistory)) {
|
||||||
await useRouter().push({
|
let history_query = {
|
||||||
path: '/events',
|
perpage: p_perpage,
|
||||||
query: {
|
page: pageNumber,
|
||||||
perpage: p_perpage,
|
}
|
||||||
page: pageNumber,
|
|
||||||
}
|
if (query.value) {
|
||||||
})
|
history_query.filter = query.value
|
||||||
|
}
|
||||||
|
|
||||||
|
await useRouter().push({ path: '/events', query: history_query })
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('paging' in json) {
|
if ('paging' in json) {
|
||||||
@@ -276,7 +287,7 @@ const deleteItem = async item => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await request(`/system/events/${item.id}`, {method: 'DELETE'})
|
const response = await request(`/system/events/${item.id}`, { method: 'DELETE' })
|
||||||
|
|
||||||
if (200 !== response.status) {
|
if (200 !== response.status) {
|
||||||
const json = await parse_api_response(response)
|
const json = await parse_api_response(response)
|
||||||
@@ -284,7 +295,9 @@ const deleteItem = async item => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
items.value = items.value.filter(i => i.id !== item.id)
|
deletedItem(item.id)
|
||||||
|
|
||||||
|
notification('success', 'Success', `Event '${makeName(item.id)}' successfully deleted.`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
notification('crit', 'Error', `Events delete Request failure. ${e.message}`
|
notification('crit', 'Error', `Events delete Request failure. ${e.message}`
|
||||||
@@ -333,7 +346,7 @@ const deleteAll = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await request(`/system/events/`, {method: 'DELETE'})
|
const response = await request(`/system/events/`, { method: 'DELETE' })
|
||||||
if (200 !== response.status) {
|
if (200 !== response.status) {
|
||||||
const json = await parse_api_response(response)
|
const json = await parse_api_response(response)
|
||||||
notification('error', 'Error', `Failed to delete events. ${json.error.code}: ${json.error.message}`)
|
notification('error', 'Error', `Failed to delete events. ${json.error.code}: ${json.error.message}`)
|
||||||
@@ -347,4 +360,43 @@ const deleteAll = async () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const deletedItem = id => {
|
||||||
|
items.value = items.value.filter(i => i.id !== id)
|
||||||
|
if (quick_view.value) {
|
||||||
|
quick_view.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(query, val => {
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
if (!val) {
|
||||||
|
if (!route?.query['filter']) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
'path': '/events',
|
||||||
|
'query': {
|
||||||
|
...route.query,
|
||||||
|
'filter': undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route?.query['filter'] === val) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
router.push({
|
||||||
|
'path': '/events',
|
||||||
|
'query': {
|
||||||
|
...route.query,
|
||||||
|
'filter': val
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="columns is-multiline">
|
|
||||||
<div class="column is-12 is-clearfix is-unselectable">
|
|
||||||
<span class="title is-4">
|
|
||||||
<span class="icon"><i class="fas fa-calendar-alt"></i> </span>
|
|
||||||
<NuxtLink to="/events" v-text="'Events'"/>
|
|
||||||
: {{ makeName(id) }}
|
|
||||||
</span>
|
|
||||||
<div class="is-pulled-right">
|
|
||||||
<div class="field is-grouped">
|
|
||||||
<div class="control has-icons-left" v-if="toggleFilter">
|
|
||||||
<input type="search" v-model.lazy="query" class="input" id="filter" placeholder="Filter">
|
|
||||||
<span class="icon is-left"><i class="fas fa-filter"/></span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control">
|
|
||||||
<button class="button is-danger is-light" @click="toggleFilter = !toggleFilter"
|
|
||||||
:disabled="!item?.logs || item.logs.length < 1"
|
|
||||||
v-tooltip.bottom="'Filter event logs.'">
|
|
||||||
<span class="icon"><i class="fas fa-filter"/></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="control">
|
|
||||||
<button class="button is-warning" @click="resetEvent(0 === item.status ? 4 : 0)"
|
|
||||||
:disabled="1 === item.status" v-tooltip.bottom="'Reset event.'">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fas"
|
|
||||||
:class="{ 'fa-trash-arrow-up': 0 !== item.status, 'fa-power-off': 0 === item.status }"></i>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<button class="button is-danger" @click="deleteItem" :disabled="1 === item.status"
|
|
||||||
v-tooltip.bottom="'Delete event.'">
|
|
||||||
<span class="icon"><i class="fas fa-trash"/></span>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<button class="button is-info" @click="loadContent()" :class="{ 'is-loading': isLoading }"
|
|
||||||
:disabled="isLoading" v-tooltip.bottom="'Reload event data.'">
|
|
||||||
<span class="icon"><i class="fas fa-sync"/></span>
|
|
||||||
</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="is-hidden-mobile">
|
|
||||||
<span class="subtitle"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column is-12" v-if="isLoading">
|
|
||||||
<Message v-if="isLoading" message_class="has-background-info-90 has-text-dark" title="Loading"
|
|
||||||
icon="fas fa-spinner fa-spin" message="Loading data. Please wait..."/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!isLoading" class="columns is-multiline">
|
|
||||||
<div class="column is-12">
|
|
||||||
<div class="notification">
|
|
||||||
<p class="title is-5">
|
|
||||||
Event <span class="tag is-info">{{ item.event }}</span>
|
|
||||||
<template v-if="item.reference">
|
|
||||||
with reference <span class="tag is-info is-light">{{ item.reference }}</span>
|
|
||||||
</template>
|
|
||||||
was created
|
|
||||||
<span class="tag is-warning">
|
|
||||||
<time class="has-tooltip" v-tooltip="moment(item.created_at).format(TOOLTIP_DATE_FORMAT)">
|
|
||||||
{{ moment(item.created_at).fromNow() }}
|
|
||||||
</time>
|
|
||||||
</span>, and last updated
|
|
||||||
<span class="tag is-danger">
|
|
||||||
<span v-if="!item.updated_at">not started</span>
|
|
||||||
<time v-else class="has-tooltip" v-tooltip="moment(item.updated_at).format(TOOLTIP_DATE_FORMAT)">
|
|
||||||
{{ moment(item.updated_at).fromNow() }}
|
|
||||||
</time>
|
|
||||||
</span>,
|
|
||||||
with status of <span class="tag" :class="getStatusClass(item.status)">{{ item.status }}:
|
|
||||||
{{ item.status_name }}</span>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column is-12" v-if="item?.event_data && Object.keys(item.event_data).length > 0">
|
|
||||||
<h2 class="title is-4 is-clickable is-unselectable" @click="toggleData = !toggleData">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fas" :class="{ 'fa-arrow-down': !toggleData, 'fa-arrow-up': toggleData }"></i>
|
|
||||||
</span>
|
|
||||||
<span>Show attached data</span>
|
|
||||||
</h2>
|
|
||||||
<div v-if="toggleData" class="is-relative">
|
|
||||||
<code style="word-break: break-word" class="is-pre-wrap is-block">
|
|
||||||
{{ JSON.stringify(item.event_data, null, 2) }}
|
|
||||||
</code>
|
|
||||||
<button class="button m-4" v-tooltip="'Copy event data'"
|
|
||||||
@click="() => copyText(JSON.stringify(item.event_data, null, 2))"
|
|
||||||
style="position: absolute; top:0; right:0;">
|
|
||||||
<span class="icon"><i class="fas fa-copy"></i></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column is-12" v-if="item?.logs && item.logs.length > 0">
|
|
||||||
<h2 class="title is-4 is-clickable is-unselectable" @click="toggleLogs = !toggleLogs">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fas" :class="{ 'fa-arrow-down': !toggleLogs, 'fa-arrow-up': toggleLogs }"></i>
|
|
||||||
</span>
|
|
||||||
<span>Show event logs</span>
|
|
||||||
</h2>
|
|
||||||
<div v-if="toggleLogs" class="is-relative">
|
|
||||||
<code style="word-break: break-word" class="is-pre-wrap is-block">
|
|
||||||
{{ JSON.stringify(filteredRows, null, 2) }}
|
|
||||||
</code>
|
|
||||||
<button class="button m-4" v-tooltip="'Copy logs'"
|
|
||||||
@click="() => copyText(JSON.stringify(filteredRows, null, 2))"
|
|
||||||
style="position: absolute; top:0; right:0;">
|
|
||||||
<span class="icon"><i class="fas fa-copy"></i></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column is-12" v-if="item?.options">
|
|
||||||
<h2 class="title is-4 is-clickable is-unselectable" @click="toggleOptions = !toggleOptions">
|
|
||||||
<span class="icon">
|
|
||||||
<i class="fas" :class="{ 'fa-arrow-down': !toggleOptions, 'fa-arrow-up': toggleOptions }"></i>
|
|
||||||
</span>
|
|
||||||
<span>Show attached options</span>
|
|
||||||
</h2>
|
|
||||||
<div v-if="toggleOptions" class="is-relative">
|
|
||||||
<code style="word-break: break-word" class="is-pre-wrap is-block">
|
|
||||||
{{ JSON.stringify(item.options, null, 2) }}
|
|
||||||
</code>
|
|
||||||
<button class="button m-4" v-tooltip="'Copy options'"
|
|
||||||
@click="() => copyText(JSON.stringify(item.options, null, 2))"
|
|
||||||
style="position: absolute; top:0; right:0;">
|
|
||||||
<span class="icon"><i class="fas fa-copy"></i></span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import {copyText, notification, parse_api_response, TOOLTIP_DATE_FORMAT} from '~/utils/index'
|
|
||||||
import request from '~/utils/request'
|
|
||||||
import moment from 'moment'
|
|
||||||
import {getStatusClass, makeName} from '~/utils/events/helpers'
|
|
||||||
import {useStorage} from '@vueuse/core'
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const id = ref(route.query.id)
|
|
||||||
|
|
||||||
const query = ref()
|
|
||||||
const item = ref({})
|
|
||||||
const isLoading = ref(true)
|
|
||||||
const toggleFilter = ref(false)
|
|
||||||
|
|
||||||
const toggleLogs = useStorage('events_toggle_logs', true)
|
|
||||||
const toggleData = useStorage('events_toggle_data', true)
|
|
||||||
const toggleOptions = useStorage('events_toggle_options', true)
|
|
||||||
|
|
||||||
watch(toggleFilter, () => {
|
|
||||||
if (!toggleFilter.value) {
|
|
||||||
query.value = ''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
if (!query.value) {
|
|
||||||
return item.value.logs ?? []
|
|
||||||
}
|
|
||||||
return item.value.logs.filter(m => m.toLowerCase().includes(query.value.toLowerCase()));
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!id.value) {
|
|
||||||
throw createError({
|
|
||||||
statusCode: 404,
|
|
||||||
message: 'Error ID not provided.'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return await loadContent()
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadContent = async () => {
|
|
||||||
try {
|
|
||||||
isLoading.value = true
|
|
||||||
const response = await request(`/system/events/${id.value}`,)
|
|
||||||
const json = await parse_api_response(response)
|
|
||||||
|
|
||||||
if (200 !== response.status) {
|
|
||||||
notification('error', 'Error', `Errors viewItem request error. ${json.error.code}: ${json.error.message}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item.value = json
|
|
||||||
|
|
||||||
useHead({title: `Event: ${json.id}`})
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
notification('crit', 'Error', `Errors viewItem Request failure. ${e.message}`
|
|
||||||
)
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteItem = async () => {
|
|
||||||
if (!confirm(`Delete '${item.value.id}'?`)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(`/system/events/${item.value.id}`, {method: 'DELETE'})
|
|
||||||
|
|
||||||
if (200 !== response.status) {
|
|
||||||
const json = await parse_api_response(response)
|
|
||||||
notification('error', 'Error', `Events view delete Request error. ${json.error.code}: ${json.error.message}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
notification('success', 'Success', `Event '${makeName(item.value.id)}' deleted.`)
|
|
||||||
await navigateTo('/events')
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
notification('crit', 'Error', `Events view delete Request failure. ${e.message}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetEvent = async (status = 0) => {
|
|
||||||
if (!confirm(`Reset '${makeName(item.value.id)}'?`)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await request(`/system/events/${item.value.id}`, {
|
|
||||||
method: 'PATCH',
|
|
||||||
body: JSON.stringify({
|
|
||||||
status: status,
|
|
||||||
reset_logs: true,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const json = await parse_api_response(response)
|
|
||||||
|
|
||||||
if (200 !== response.status) {
|
|
||||||
notification('error', 'Error', `Events view patch Request error. ${json.error.code}: ${json.error.message}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item.value = json
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e)
|
|
||||||
notification('crit', 'Error', `Events view patch Request failure. ${e.message}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
Reference in New Issue
Block a user