twitch-bot/src/app.vue

331 lines
8.3 KiB
Vue

<template>
<div>
<b-navbar
toggleable="lg"
type="dark"
variant="primary"
class="mb-3"
>
<b-navbar-brand :to="{ name: 'general-config' }">
<font-awesome-icon
fixed-width
class="mr-1"
:icon="['fas', 'robot']"
/>
Twitch-Bot
</b-navbar-brand>
<b-navbar-toggle target="nav-collapse" />
<b-collapse
id="nav-collapse"
is-nav
>
<b-navbar-nav v-if="isAuthenticated">
<b-nav-item
:to="{ name: 'general-config' }"
>
<font-awesome-icon
fixed-width
class="mr-1"
:icon="['fas', 'cog']"
/>
General
</b-nav-item>
<b-nav-item
:to="{ name: 'edit-automessages' }"
>
<font-awesome-icon
fixed-width
class="mr-1"
:icon="['fas', 'envelope-open-text']"
/>
Auto-Messages
</b-nav-item>
<b-nav-item
:to="{ name: 'edit-rules' }"
>
<font-awesome-icon
fixed-width
class="mr-1"
:icon="['fas', 'inbox']"
/>
Rules
</b-nav-item>
</b-navbar-nav>
<b-navbar-nav class="ml-auto">
<b-nav-text
v-if="loadingData"
>
<font-awesome-icon
fixed-width
class="text-warning"
:icon="['fas', 'spinner']"
pulse
/>
</b-nav-text>
<b-nav-text
class="ml-2"
>
<template
v-for="check in status.checks"
>
<font-awesome-icon
:id="`statusCheck${check.name}`"
:key="check.key"
fixed-width
:class="{ 'text-danger': !check.success, 'text-success': check.success }"
:icon="['fas', 'question-circle']"
/>
<b-tooltip
:key="check.key"
:target="`statusCheck${check.name}`"
triggers="hover"
>
{{ check.description }}
</b-tooltip>
</template>
</b-nav-text>
<b-nav-text class="ml-2">
<font-awesome-icon
v-if="configNotifySocketConnected"
id="socketConnectionStatus"
fixed-width
class="mr-1 text-success"
:icon="['fas', 'ethernet']"
/>
<font-awesome-icon
v-else
id="socketConnectionStatus"
fixed-width
class="mr-1 text-danger"
:icon="['fas', 'ethernet']"
/>
<b-tooltip
target="socketConnectionStatus"
triggers="hover"
>
<span v-if="configNotifySocketConnected">Connected to Bot</span>
<span v-else>Disconnected from Bot</span>
</b-tooltip>
</b-nav-text>
</b-navbar-nav>
</b-collapse>
</b-navbar>
<b-container>
<!-- Error display -->
<b-row
v-if="error"
class="sticky-row"
>
<b-col>
<b-alert
dismissible
show
variant="danger"
@dismissed="error = null"
>
<font-awesome-icon
fixed-width
class="mr-1"
:icon="['fas', 'exclamation-circle']"
/>
{{ error }}
</b-alert>
</b-col>
</b-row>
<!-- Working display -->
<b-row
v-if="changePending"
class="sticky-row"
>
<b-col>
<b-alert
show
variant="info"
>
<font-awesome-icon
fixed-width
class="mr-1"
:icon="['fas', 'spinner']"
pulse
/>
Your change was submitted and is pending, please wait for config to be updated!
</b-alert>
</b-col>
</b-row>
<!-- Logged-out state -->
<b-row
v-if="!isAuthenticated"
>
<b-col
class="text-center"
>
<b-button
:disabled="!$root.vars.TwitchClientID"
:href="authURL"
variant="twitch"
>
<font-awesome-icon
fixed-width
class="mr-1"
:icon="['fab', 'twitch']"
/>
Login with Twitch
</b-button>
</b-col>
</b-row>
<!-- Logged-in state -->
<router-view v-else />
</b-container>
</div>
</template>
<script>
import * as constants from './const.js'
import axios from 'axios'
export default {
computed: {
authURL() {
const scopes = []
const params = new URLSearchParams()
params.set('client_id', this.$root.vars.TwitchClientID)
params.set('redirect_uri', window.location.href.split('#')[0])
params.set('response_type', 'token')
params.set('scope', scopes.join(' '))
return `https://id.twitch.tv/oauth2/authorize?${params.toString()}`
},
},
created() {
this.$bus.$on(constants.NOTIFY_CHANGE_PENDING, p => {
this.changePending = Boolean(p)
})
this.$bus.$on(constants.NOTIFY_ERROR, err => {
this.error = err
})
this.$bus.$on(constants.NOTIFY_FETCH_ERROR, err => {
this.handleFetchError(err)
})
this.$bus.$on(constants.NOTIFY_LOADING_DATA, l => {
this.loadingData = Boolean(l)
})
},
data() {
return {
changePending: false,
configNotifyBackoff: 100,
configNotifySocket: null,
configNotifySocketConnected: false,
error: null,
loadingData: false,
status: {},
}
},
methods: {
fetchStatus() {
return axios.get('status/status.json?fail-status=200')
.then(resp => {
this.status = resp.data
})
.catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
},
handleFetchError(err) {
switch (err.response.status) {
case 403:
this.$root.authToken = null
this.error = 'This user is not authorized for the config editor'
break
case 502:
this.error = 'Looks like the bot is currently not reachable. Please check it is running and refresh the interface.'
break
default:
this.error = `Something went wrong: ${err.response.data} (${err.response.status})`
}
},
openConfigNotifySocket() {
if (this.configNotifySocket) {
this.configNotifySocket.close()
this.configNotifySocket = null
}
const updateBackoffAndReconnect = () => {
this.configNotifyBackoff = Math.min(this.configNotifyBackoff * 1.5, 10000)
window.setTimeout(() => this.openConfigNotifySocket(), this.configNotifyBackoff)
}
this.configNotifySocket = new WebSocket(`${window.location.href.split('#')[0].replace(/^http/, 'ws')}config-editor/notify-config`)
this.configNotifySocket.onopen = () => {
console.debug('[notify] Socket connected')
this.configNotifySocketConnected = true
}
this.configNotifySocket.onmessage = evt => {
const msg = JSON.parse(evt.data)
console.debug(`[notify] Socket message received type=${msg.msg_type}`)
this.configNotifyBackoff = 100 // We've received a message, reset backoff
if (msg.msg_type === constants.NOTIFY_CONFIG_RELOAD) {
this.$bus.$emit(constants.NOTIFY_CONFIG_RELOAD)
}
}
this.configNotifySocket.onclose = evt => {
console.debug(`[notify] Socket was closed wasClean=${evt.wasClean}`)
this.configNotifySocketConnected = false
updateBackoffAndReconnect()
}
},
},
mounted() {
if (this.isAuthenticated) {
this.openConfigNotifySocket()
}
window.setInterval(() => this.fetchStatus(), 10000)
this.fetchStatus()
},
name: 'TwitchBotEditorApp',
props: {
isAuthenticated: {
required: true,
type: Boolean,
},
},
watch: {
isAuthenticated(to) {
if (to && !this.configNotifySocketConnected) {
this.openConfigNotifySocket()
}
},
},
}
</script>
<style>
.btn-twitch {
background-color: #6441a5;
}
.sticky-row {
position: sticky;
top: 0;
}
</style>