forked from platypush/platypush
[#437] Added application panel with events monitor tab.
This commit is contained in:
parent
82e796e20b
commit
51d0b21162
13 changed files with 871 additions and 15 deletions
|
@ -53,6 +53,8 @@ export default {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bus.emit('event', event)
|
||||||
|
|
||||||
if (null in this.handlers) { // lgtm [js/implicit-operand-conversion]
|
if (null in this.handlers) { // lgtm [js/implicit-operand-conversion]
|
||||||
handlers.push(this.handlers[null]) // lgtm [js/implicit-operand-conversion]
|
handlers.push(this.handlers[null]) // lgtm [js/implicit-operand-conversion]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
"alarm": {
|
"alarm": {
|
||||||
"class": "fas fa-stopwatch"
|
"class": "fas fa-stopwatch"
|
||||||
},
|
},
|
||||||
|
"application": {
|
||||||
|
"class": "fas fa-sliders"
|
||||||
|
},
|
||||||
"arduino": {
|
"arduino": {
|
||||||
"class": "fas fa-microchip"
|
"class": "fas fa-microchip"
|
||||||
},
|
},
|
||||||
|
|
|
@ -117,7 +117,7 @@ export default {
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
specialPlugins() {
|
specialPlugins() {
|
||||||
return ['execute', 'entities', 'file', 'procedures']
|
return ['execute', 'entities', 'file', 'application', 'procedures']
|
||||||
},
|
},
|
||||||
|
|
||||||
panelNames() {
|
panelNames() {
|
||||||
|
@ -132,6 +132,7 @@ export default {
|
||||||
let panelNames = Object.keys(this.panels).sort()
|
let panelNames = Object.keys(this.panels).sort()
|
||||||
panelNames = prepend(panelNames, 'file')
|
panelNames = prepend(panelNames, 'file')
|
||||||
panelNames = prepend(panelNames, 'procedures')
|
panelNames = prepend(panelNames, 'procedures')
|
||||||
|
panelNames = prepend(panelNames, 'application')
|
||||||
panelNames = prepend(panelNames, 'execute')
|
panelNames = prepend(panelNames, 'execute')
|
||||||
panelNames = prepend(panelNames, 'entities')
|
panelNames = prepend(panelNames, 'entities')
|
||||||
return panelNames
|
return panelNames
|
||||||
|
@ -151,16 +152,20 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
displayName(name) {
|
displayName(name) {
|
||||||
if (name === 'entities')
|
switch (name) {
|
||||||
return 'Home'
|
case 'application':
|
||||||
if (name === 'execute')
|
return 'Application'
|
||||||
return 'Execute'
|
case 'entities':
|
||||||
if (name === 'file')
|
return 'Home'
|
||||||
return 'Files'
|
case 'execute':
|
||||||
if (name === 'procedures')
|
return 'Execute'
|
||||||
return 'Procedures'
|
case 'file':
|
||||||
|
return 'Files'
|
||||||
return name
|
case 'procedures':
|
||||||
|
return 'Procedures'
|
||||||
|
default:
|
||||||
|
return name
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setConnected(connected) {
|
setConnected(connected) {
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
<template>
|
||||||
|
<a class="event renderer"
|
||||||
|
:class="{even: index % 2 === 0, odd: index % 2 !== 0, expanded}"
|
||||||
|
:href="href"
|
||||||
|
@click.prevent.stop="onClick">
|
||||||
|
<div class="header">
|
||||||
|
<div class="col-11 title">
|
||||||
|
<div class="time-container">
|
||||||
|
[<span class="time">{{ time }}</span>]
|
||||||
|
</div>
|
||||||
|
<div class="type-container">
|
||||||
|
<span class="type">{{ type }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-1 buttons">
|
||||||
|
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
||||||
|
<DropdownItem text="Raw Event" icon-class="fa fa-file-code" @input="showEditor = true" />
|
||||||
|
<DropdownItem text="Copy to Clipboard" icon-class="fa fa-copy" @input="copy" />
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body">
|
||||||
|
<div class="expanded" @click.stop v-if="expanded">
|
||||||
|
<div class="row time">
|
||||||
|
<span class="key"><i class="fas fa-clock"></i> Time</span>
|
||||||
|
<span class="value scalar">{{ datetime }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row type">
|
||||||
|
<span class="key"><i class="fas fa-tag"></i> Type</span>
|
||||||
|
<span class="value scalar">
|
||||||
|
<a v-if="typeDocHref" :href="typeDocHref" target="_blank">{{ type }}</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row id">
|
||||||
|
<span class="key"><i class="fas fa-id-badge"></i> ID</span>
|
||||||
|
<span class="value scalar">{{ output?.id }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row origin">
|
||||||
|
<span class="key"><i class="fas fa-map-marker-alt"></i> Origin</span>
|
||||||
|
<span class="value scalar">{{ output?.origin }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row args">
|
||||||
|
<span class="key"><i class="fas fa-cogs"></i> Args</span>
|
||||||
|
<span class="value object">
|
||||||
|
<ObjectRenderer :output="output.args" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="editor-container" v-if="showEditor">
|
||||||
|
<FileEditor :file="type.split('.').pop()"
|
||||||
|
:text="indentedOutput"
|
||||||
|
:visible="true"
|
||||||
|
:uppercase="false"
|
||||||
|
:with-save="false"
|
||||||
|
content-type="json"
|
||||||
|
@close="showEditor = false" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
|
import FileEditor from "@/components/File/EditorModal";
|
||||||
|
import Mixin from './Mixin'
|
||||||
|
import ObjectRenderer from './ObjectRenderer'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [Mixin],
|
||||||
|
components: {
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
FileEditor,
|
||||||
|
ObjectRenderer,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showEditor: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
datetime() {
|
||||||
|
const timestamp = this.output?.timestamp || this.output?._timestamp
|
||||||
|
if (!timestamp) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.formatDateTime(timestamp)
|
||||||
|
},
|
||||||
|
|
||||||
|
indentedOutput() {
|
||||||
|
if (!Object.keys(this.output || {})?.length) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(this.output, null, 2)
|
||||||
|
} catch (err) {
|
||||||
|
return this.output
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
time() {
|
||||||
|
const timestamp = this.output?.timestamp || this.output?._timestamp
|
||||||
|
if (!timestamp) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.formatTime(timestamp)
|
||||||
|
},
|
||||||
|
|
||||||
|
href() {
|
||||||
|
const route = this.$route.fullPath
|
||||||
|
if (route.match(/&index=\d+/)) {
|
||||||
|
return route.replace(/&index=\d+/, `&index=${this.index}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return route + (
|
||||||
|
this.index != null ? `&index=${this.index}` : ''
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
type() {
|
||||||
|
return this.output?.args?.type
|
||||||
|
},
|
||||||
|
|
||||||
|
typeDocHref() {
|
||||||
|
if (!this.type?.length) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = this.type
|
||||||
|
.replace(/^platypush\.message\.event\./, '')
|
||||||
|
.split('.')
|
||||||
|
|
||||||
|
const module = parts.splice(0, parts.length - 1).join('.')
|
||||||
|
return `https://docs.platypush.tech/platypush/events/${module}.html#${this.type}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async copy() {
|
||||||
|
await this.copyToClipboard(this.indentedOutput)
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick() {
|
||||||
|
this.expanded = !this.expanded
|
||||||
|
this.setUrlArgs({index: this.expanded && this.index != null ? this.index : undefined})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
const args = this.getUrlArgs()
|
||||||
|
if (args.index == this.index?.toString()) {
|
||||||
|
this.expanded = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "./style.scss";
|
||||||
|
|
||||||
|
.renderer {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background: $default-bg-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0.5em auto;
|
||||||
|
padding: 0 1em;
|
||||||
|
border-radius: 0.5em;
|
||||||
|
border: $default-border-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,67 @@
|
||||||
|
<script>
|
||||||
|
import 'highlight.js/lib/common'
|
||||||
|
import 'highlight.js/styles/night-owl.min.css'
|
||||||
|
import hljs from "highlight.js"
|
||||||
|
|
||||||
|
import Utils from '@/Utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [Utils],
|
||||||
|
props: {
|
||||||
|
output: {
|
||||||
|
type: [Object, String],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
filter: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
|
||||||
|
index: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
expanded: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
highlightedText() {
|
||||||
|
return hljs.highlight(
|
||||||
|
this.outputString,
|
||||||
|
{language: this.isJson ? 'json' : 'plaintext'}
|
||||||
|
).value
|
||||||
|
},
|
||||||
|
|
||||||
|
isJson() {
|
||||||
|
if (typeof this.output === 'object') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSON.parse(this.output)
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
outputString() {
|
||||||
|
if (!Object.keys(this.output || {})?.length) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.stringify(this.output, null, this.expanded ? 2 : 0)
|
||||||
|
} catch (err) {
|
||||||
|
return this.output
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,69 @@
|
||||||
|
<template>
|
||||||
|
<a class="object renderer" :href="$route.fullPath"
|
||||||
|
@click.prevent.stop="onClick">
|
||||||
|
<div class="compact" v-if="!expanded">
|
||||||
|
<i class="toggler fas fa-caret-right" />
|
||||||
|
<span class="delimiter" v-text="typeof output === 'object' ? '{' : '['" />
|
||||||
|
<span class="ellipsis">...</span>
|
||||||
|
<span class="delimiter" v-text="typeof output === 'object' ? '}' : ']'" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="expanded" v-else>
|
||||||
|
<i class="toggler fas fa-caret-down" />
|
||||||
|
<div class="rows">
|
||||||
|
<div class="row"
|
||||||
|
:class="{even: index % 2 === 0, odd: index % 2 !== 0, args: (value instanceof Object) || Array.isArray(value)}"
|
||||||
|
v-for="(value, key, index) in output"
|
||||||
|
:key="key">
|
||||||
|
<span class="key" v-text="key" />
|
||||||
|
<span class="value scalar" v-text="value" v-if="!(value instanceof Object) && !Array.isArray(value)" />
|
||||||
|
<span class="value object" v-else>
|
||||||
|
<ObjectRenderer :output="value" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Mixin from './Mixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ObjectRenderer',
|
||||||
|
mixins: [Mixin],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onClick() {
|
||||||
|
this.expanded = !this.expanded
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "./style.scss";
|
||||||
|
|
||||||
|
.renderer {
|
||||||
|
.key {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggler {
|
||||||
|
margin: 0.75em 0.75em 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $default-hover-fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<div class="string renderer">
|
||||||
|
<pre><code v-html="highlightedText" /></pre>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Mixin from './Mixin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [Mixin],
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "./style.scss";
|
||||||
|
</style>
|
|
@ -0,0 +1,142 @@
|
||||||
|
.renderer {
|
||||||
|
--default-bg: #{$background-color};
|
||||||
|
--even-color: #{$default-bg-2};
|
||||||
|
--odd-color: inherit;
|
||||||
|
--hover-bg: #{$hover-bg};
|
||||||
|
--text-color: #{$default-fg};
|
||||||
|
--time-color: #{$no-items-color};
|
||||||
|
--type-color: #{$default-fg-2};
|
||||||
|
|
||||||
|
&.dark {
|
||||||
|
--default-bg: black;
|
||||||
|
--even-color: #141414;
|
||||||
|
--odd-color: inherit;
|
||||||
|
--hover-bg: #333;
|
||||||
|
--text-color: #fff;
|
||||||
|
--time-color: #999;
|
||||||
|
--type-color: #fbf6bb;
|
||||||
|
}
|
||||||
|
|
||||||
|
width: fit-content;
|
||||||
|
min-width: 100%;
|
||||||
|
color: var(--text-color);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: initial;
|
||||||
|
|
||||||
|
&.even, .even {
|
||||||
|
background: var(--even-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.odd, .odd {
|
||||||
|
background: var(--odd-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.expanded, .editor-container {
|
||||||
|
color: initial !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header, .expanded {
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--hover-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include from($tablet) {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.expanded {
|
||||||
|
.header {
|
||||||
|
padding: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
border-bottom: 1px solid var(--time-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded {
|
||||||
|
background: var(--default-bg);
|
||||||
|
cursor: default;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.rows {
|
||||||
|
width: calc(100% - 1.35em);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25em 0;
|
||||||
|
margin: 0.25em 0;
|
||||||
|
|
||||||
|
@include until($tablet) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.args {
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.key {
|
||||||
|
@extend .col-s-12, .col-m-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
display: flex;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
@include from($tablet) {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.scalar {
|
||||||
|
@extend .col-s-12, .col-m-8;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.object {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time {
|
||||||
|
color: var(--time-color);
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
|
color: var(--type-color);
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: initial;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,10 +13,15 @@
|
||||||
<script>
|
<script>
|
||||||
import RestartButton from "@/components/elements/RestartButton"
|
import RestartButton from "@/components/elements/RestartButton"
|
||||||
import StopButton from "@/components/elements/StopButton"
|
import StopButton from "@/components/elements/StopButton"
|
||||||
|
import Utils from '@/Utils'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Application",
|
mixins: [Utils],
|
||||||
components: {RestartButton, StopButton},
|
components: {RestartButton, StopButton},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.setUrlArgs({ view: 'actions' })
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
<template>
|
||||||
|
<div class="events-container">
|
||||||
|
<div class="header">
|
||||||
|
<div class="filter-container">
|
||||||
|
<input type="text" v-model="filter" placeholder="Filter events" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-container">
|
||||||
|
<button @click="running = !running" :title="(running ? 'Pause' : 'Start') + ' capturing'">
|
||||||
|
<i :class="running ? 'fa fa-pause' : 'fa fa-play'" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<Dropdown title="Actions" icon-class="fa fa-ellipsis-h">
|
||||||
|
<DropdownItem :text="follow ? 'Unfollow' : 'Follow'" icon-class="fa fa-eye" @input="follow = !follow" />
|
||||||
|
<DropdownItem text="Export Events" icon-class="fa fa-download" @input="download" />
|
||||||
|
<DropdownItem text="Clear Events" icon-class="fa fa-trash" @input="clear" />
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="body" ref="body">
|
||||||
|
<EventRenderer v-for="(event, index) in filteredEvents"
|
||||||
|
:key="index"
|
||||||
|
:index="index"
|
||||||
|
:output="event" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<Loading v-if="running" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Dropdown from "@/components/elements/Dropdown";
|
||||||
|
import DropdownItem from "@/components/elements/DropdownItem";
|
||||||
|
import EventRenderer from "@/components/elements/OutputRenderers/EventRenderer";
|
||||||
|
import Loading from "@/components/Loading";
|
||||||
|
import Utils from '@/Utils'
|
||||||
|
import { bus } from "@/bus";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [Utils],
|
||||||
|
components: {
|
||||||
|
Dropdown,
|
||||||
|
DropdownItem,
|
||||||
|
EventRenderer,
|
||||||
|
Loading,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
filter: '',
|
||||||
|
follow: true,
|
||||||
|
output: [],
|
||||||
|
running: true,
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
filteredEvents() {
|
||||||
|
const filter = this.filter?.toLowerCase()
|
||||||
|
return Object.keys(this.serializedEvents)
|
||||||
|
.filter((i) => {
|
||||||
|
if (!filter?.length) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.serializedEvents[i].includes(filter)
|
||||||
|
})
|
||||||
|
.map((i) => this.outputObjects[i])
|
||||||
|
},
|
||||||
|
|
||||||
|
outputString() {
|
||||||
|
return this.outputStrings.join('\n')
|
||||||
|
},
|
||||||
|
|
||||||
|
outputObjects() {
|
||||||
|
return this.output.map((item) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(item)
|
||||||
|
} catch (err) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
outputStrings() {
|
||||||
|
return this.output.map((item) => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(item)
|
||||||
|
} catch (err) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
serializedEvents() {
|
||||||
|
return this.outputObjects.map((item) => {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(item).toLowerCase()
|
||||||
|
} catch (err) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
clear() {
|
||||||
|
this.output = []
|
||||||
|
},
|
||||||
|
|
||||||
|
download() {
|
||||||
|
const blob = new Blob([this.outputString], { type: 'application/json' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = `events-${new Date().toISOString()}.json`
|
||||||
|
a.click()
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
},
|
||||||
|
|
||||||
|
onEvent(msg) {
|
||||||
|
if (!this.running) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.output.push(msg)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
output: {
|
||||||
|
deep: true,
|
||||||
|
handler() {
|
||||||
|
if (!this.follow) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.body.scrollTop = this.$refs.body.scrollHeight
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.setUrlArgs({ view: 'events' })
|
||||||
|
bus.on('event', this.onEvent)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$header-height: 3.25em;
|
||||||
|
$header-margin: 0.25em;
|
||||||
|
$footer-height: 2em;
|
||||||
|
$btn-container-width: 5em;
|
||||||
|
|
||||||
|
.events-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
background: $background-color;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
width: 100%;
|
||||||
|
height: $header-height;
|
||||||
|
margin-bottom: $header-margin;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: $default-bg-4;
|
||||||
|
padding: 0 0.5em;
|
||||||
|
box-shadow: $border-shadow-bottom;
|
||||||
|
|
||||||
|
.filter-container {
|
||||||
|
width: calc(100% - #{$btn-container-width});
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
width: $btn-container-width;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0.5em;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 40em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - #{$header-height} - #{$header-margin} - #{$footer-height});
|
||||||
|
position: relative;
|
||||||
|
margin: 0 0 $footer-height 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
width: 100%;
|
||||||
|
height: $footer-height;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
font-size: 0.75em;
|
||||||
|
background: $default-bg-4;
|
||||||
|
box-shadow: $border-shadow-top;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<div class="tabs">
|
||||||
|
<Tabs>
|
||||||
|
<Tab :selected="selectedView === 'actions'"
|
||||||
|
icon-class="fas fa-cogs"
|
||||||
|
@input="selectedView = 'actions'">
|
||||||
|
Actions
|
||||||
|
</Tab>
|
||||||
|
|
||||||
|
<Tab :selected="selectedView === 'events'"
|
||||||
|
icon-class="fas fa-bolt"
|
||||||
|
@input="selectedView = 'events'">
|
||||||
|
Events
|
||||||
|
</Tab>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<Actions v-if="selectedView === 'actions'" />
|
||||||
|
<Events v-else-if="selectedView === 'events'" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Actions from "./Actions"
|
||||||
|
import Events from "./Events"
|
||||||
|
import Tabs from "@/components/elements/Tabs"
|
||||||
|
import Tab from "@/components/elements/Tab"
|
||||||
|
import Utils from '@/Utils'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [Utils],
|
||||||
|
components: {
|
||||||
|
Actions,
|
||||||
|
Events,
|
||||||
|
Tab,
|
||||||
|
Tabs,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedView: 'actions',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
setView(view) {
|
||||||
|
if (!view?.length) {
|
||||||
|
const urlArgs = this.getUrlArgs()
|
||||||
|
if (urlArgs.view?.length) {
|
||||||
|
view = urlArgs.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view?.length) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedView = view
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
$route() {
|
||||||
|
this.setView()
|
||||||
|
},
|
||||||
|
|
||||||
|
selectedView() {
|
||||||
|
this.setUrlArgs({ view: this.selectedView })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.setView()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$tabs-height: 3.5em;
|
||||||
|
|
||||||
|
.app-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
:deep(.tabs) {
|
||||||
|
width: 100%;
|
||||||
|
height: $tabs-height;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: -0.1em;
|
||||||
|
box-shadow: $border-shadow-bottom;
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
height: $tabs-height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: calc(100vh - #{$tabs-height});
|
||||||
|
background-color: $background-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
<main>
|
<main>
|
||||||
<Application v-if="selectedPanel === 'application'" />
|
|
||||||
<Users :session-token="sessionToken" :current-user="currentUser"
|
<Users :session-token="sessionToken" :current-user="currentUser"
|
||||||
v-if="selectedPanel === 'users' && currentUser" />
|
v-if="selectedPanel === 'users' && currentUser" />
|
||||||
<Tokens :current-user="currentUser"
|
<Tokens :current-user="currentUser"
|
||||||
|
@ -11,14 +10,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Application from "@/components/panels/Settings/Application";
|
|
||||||
import Tokens from "@/components/panels/Settings/Tokens/Index";
|
import Tokens from "@/components/panels/Settings/Tokens/Index";
|
||||||
import Users from "@/components/panels/Settings/Users";
|
import Users from "@/components/panels/Settings/Users";
|
||||||
import Utils from "@/Utils";
|
import Utils from "@/Utils";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Settings",
|
name: "Settings",
|
||||||
components: {Application, Users, Tokens},
|
components: {Users, Tokens},
|
||||||
mixins: [Utils],
|
mixins: [Utils],
|
||||||
emits: ['change-page'],
|
emits: ['change-page'],
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ export default {
|
||||||
initializeDefaultViews() {
|
initializeDefaultViews() {
|
||||||
this.plugins.entities = {}
|
this.plugins.entities = {}
|
||||||
this.plugins.execute = {}
|
this.plugins.execute = {}
|
||||||
|
this.plugins.application = {}
|
||||||
this.plugins.file = this.plugins.file || {}
|
this.plugins.file = this.plugins.file || {}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue