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
|
||||
}
|
||||
|
||||
bus.emit('event', event)
|
||||
|
||||
if (null in this.handlers) { // lgtm [js/implicit-operand-conversion]
|
||||
handlers.push(this.handlers[null]) // lgtm [js/implicit-operand-conversion]
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
"alarm": {
|
||||
"class": "fas fa-stopwatch"
|
||||
},
|
||||
"application": {
|
||||
"class": "fas fa-sliders"
|
||||
},
|
||||
"arduino": {
|
||||
"class": "fas fa-microchip"
|
||||
},
|
||||
|
|
|
@ -117,7 +117,7 @@ export default {
|
|||
|
||||
computed: {
|
||||
specialPlugins() {
|
||||
return ['execute', 'entities', 'file', 'procedures']
|
||||
return ['execute', 'entities', 'file', 'application', 'procedures']
|
||||
},
|
||||
|
||||
panelNames() {
|
||||
|
@ -132,6 +132,7 @@ export default {
|
|||
let panelNames = Object.keys(this.panels).sort()
|
||||
panelNames = prepend(panelNames, 'file')
|
||||
panelNames = prepend(panelNames, 'procedures')
|
||||
panelNames = prepend(panelNames, 'application')
|
||||
panelNames = prepend(panelNames, 'execute')
|
||||
panelNames = prepend(panelNames, 'entities')
|
||||
return panelNames
|
||||
|
@ -151,16 +152,20 @@ export default {
|
|||
},
|
||||
|
||||
displayName(name) {
|
||||
if (name === 'entities')
|
||||
return 'Home'
|
||||
if (name === 'execute')
|
||||
return 'Execute'
|
||||
if (name === 'file')
|
||||
return 'Files'
|
||||
if (name === 'procedures')
|
||||
return 'Procedures'
|
||||
|
||||
return name
|
||||
switch (name) {
|
||||
case 'application':
|
||||
return 'Application'
|
||||
case 'entities':
|
||||
return 'Home'
|
||||
case 'execute':
|
||||
return 'Execute'
|
||||
case 'file':
|
||||
return 'Files'
|
||||
case 'procedures':
|
||||
return 'Procedures'
|
||||
default:
|
||||
return name
|
||||
}
|
||||
},
|
||||
|
||||
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>
|
||||
import RestartButton from "@/components/elements/RestartButton"
|
||||
import StopButton from "@/components/elements/StopButton"
|
||||
import Utils from '@/Utils'
|
||||
|
||||
export default {
|
||||
name: "Application",
|
||||
mixins: [Utils],
|
||||
components: {RestartButton, StopButton},
|
||||
|
||||
mounted() {
|
||||
this.setUrlArgs({ view: 'actions' })
|
||||
},
|
||||
}
|
||||
</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>
|
||||
<div class="settings-container">
|
||||
<main>
|
||||
<Application v-if="selectedPanel === 'application'" />
|
||||
<Users :session-token="sessionToken" :current-user="currentUser"
|
||||
v-if="selectedPanel === 'users' && currentUser" />
|
||||
<Tokens :current-user="currentUser"
|
||||
|
@ -11,14 +10,13 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import Application from "@/components/panels/Settings/Application";
|
||||
import Tokens from "@/components/panels/Settings/Tokens/Index";
|
||||
import Users from "@/components/panels/Settings/Users";
|
||||
import Utils from "@/Utils";
|
||||
|
||||
export default {
|
||||
name: "Settings",
|
||||
components: {Application, Users, Tokens},
|
||||
components: {Users, Tokens},
|
||||
mixins: [Utils],
|
||||
emits: ['change-page'],
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ export default {
|
|||
initializeDefaultViews() {
|
||||
this.plugins.entities = {}
|
||||
this.plugins.execute = {}
|
||||
this.plugins.application = {}
|
||||
this.plugins.file = this.plugins.file || {}
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue