update console and logs stream to use fetchEventStream
This commit is contained in:
@@ -128,7 +128,8 @@ import {FitAddon} from "@xterm/addon-fit"
|
||||
import {useStorage} from '@vueuse/core'
|
||||
import {disableOpacity, enableOpacity, notification} from '~/utils/index'
|
||||
import Message from '~/components/Message'
|
||||
import request from "~/utils/request.js";
|
||||
import request from "~/utils/request.js"
|
||||
import {fetchEventSource} from '@microsoft/fetch-event-source'
|
||||
|
||||
useHead({title: `Console`})
|
||||
|
||||
@@ -150,11 +151,10 @@ const hasPrefix = computed(() => command.value.startsWith('console') || command.
|
||||
const hasPlaceholder = computed(() => command.value && command.value.match(/\[.*]/))
|
||||
const show_page_tips = useStorage('show_page_tips', true)
|
||||
const allEnabled = ref(false)
|
||||
const ctrl = new AbortController();
|
||||
|
||||
const RunCommand = async () => {
|
||||
const api_path = useStorage('api_path', '/v1/api')
|
||||
const api_url = useStorage('api_url', '')
|
||||
const api_token = useStorage('api_token', '')
|
||||
const token = useStorage('token', '')
|
||||
|
||||
/** @type {string} */
|
||||
let userCommand = command.value
|
||||
@@ -200,7 +200,7 @@ const RunCommand = async () => {
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
let token;
|
||||
let commandToken;
|
||||
|
||||
try {
|
||||
const response = await request('/system/command', {
|
||||
@@ -215,29 +215,54 @@ const RunCommand = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
token = json.token
|
||||
commandToken = json.token
|
||||
} catch (e) {
|
||||
await finished()
|
||||
notification('error', 'Error', e.message, 5000)
|
||||
return;
|
||||
}
|
||||
|
||||
sse = new EventSource(`${api_url.value}${api_path.value}/system/command/${token}?apikey=${api_token.value}`)
|
||||
sse = fetchEventSource(`/v1/api/system/command/${commandToken}`, {
|
||||
signal: ctrl.signal,
|
||||
headers: {'Authorization': `Token ${token.value}`},
|
||||
async onmessage(evt) {
|
||||
switch (evt.event) {
|
||||
case 'data':
|
||||
terminal.value.write(JSON.parse(evt.data).data)
|
||||
break
|
||||
case 'close':
|
||||
await finished()
|
||||
break
|
||||
case 'exit_code':
|
||||
exitCode.value = parseInt(evt.data)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
},
|
||||
async onopen(response) {
|
||||
if (response.ok) {
|
||||
return
|
||||
}
|
||||
|
||||
const json = await parse_api_response(response)
|
||||
const message = `${json.error.code}: ${json.error.message}`
|
||||
notification('error', 'Error', message, 3000)
|
||||
await finished()
|
||||
},
|
||||
async onerror() {
|
||||
await finished()
|
||||
},
|
||||
})
|
||||
|
||||
if ('' !== command.value) {
|
||||
terminal.value.writeln(`(${exitCode.value}) ~ ${userCommand}`)
|
||||
}
|
||||
|
||||
sse.addEventListener('data', async e => terminal.value.write(JSON.parse(e.data).data))
|
||||
sse.addEventListener('close', async () => finished())
|
||||
sse.addEventListener('exit_code', async e => exitCode.value = e.data)
|
||||
sse.onclose = async () => finished()
|
||||
sse.onerror = async () => finished()
|
||||
}
|
||||
|
||||
const finished = async () => {
|
||||
if (sse) {
|
||||
sse.close()
|
||||
ctrl.abort();
|
||||
}
|
||||
|
||||
isLoading.value = false
|
||||
@@ -287,7 +312,7 @@ const clearOutput = async () => {
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", reSizeTerminal)
|
||||
if (sse) {
|
||||
sse.close()
|
||||
ctrl.abort();
|
||||
}
|
||||
enableOpacity()
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<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-globe" :class="{ 'fa-spin': isLoading }" /> </span>
|
||||
<span class="icon"><i class="fas fa-globe" :class="{ 'fa-spin': isLoading }"/> </span>
|
||||
<NuxtLink to="/logs">Logs</NuxtLink>
|
||||
: {{ filename }}
|
||||
</span>
|
||||
@@ -12,46 +12,46 @@
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<button v-if="!autoScroll" @click="scrollToBottom" class="button is-primary"
|
||||
v-tooltip.bottom="'Go to bottom'">
|
||||
v-tooltip.bottom="'Go to bottom'">
|
||||
<span class="icon"><i class="fas fa-arrow-down"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
<span class="icon is-left"><i class="fas fa-filter"/></span>
|
||||
</div>
|
||||
|
||||
<div class="control">
|
||||
<button class="button is-danger is-light" v-tooltip.bottom="'Filter log lines.'"
|
||||
@click="toggleFilter = !toggleFilter">
|
||||
<span class="icon"><i class="fas fa-filter" /></span>
|
||||
@click="toggleFilter = !toggleFilter">
|
||||
<span class="icon"><i class="fas fa-filter"/></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="control">
|
||||
<button class="button is-danger" v-tooltip.bottom="'Delete Logfile.'" @click="deleteFile">
|
||||
<span class="icon"><i class="fas fa-trash" /></span>
|
||||
<span class="icon"><i class="fas fa-trash"/></span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p class="control">
|
||||
<button class="button is-purple is-light" v-tooltip.bottom="'Download the entire logfile.'"
|
||||
@click="downloadFile" :class="{ 'is-loading': isDownloading }">
|
||||
<span class="icon"><i class="fas fa-download" /></span>
|
||||
@click="downloadFile" :class="{ 'is-loading': isDownloading }">
|
||||
<span class="icon"><i class="fas fa-download"/></span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p class="control">
|
||||
<button class="button is-warning" @click="wrapLines = !wrapLines" v-tooltip.bottom="'Toggle wrap line'">
|
||||
<span class="icon"><i class="fas fa-text-width" /></span>
|
||||
<span class="icon"><i class="fas fa-text-width"/></span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p class="control">
|
||||
<button class="button" v-tooltip.bottom="'Copy showing logs'"
|
||||
@click="() => copyText(filterItems.map(i => i.text).join('\n'))">
|
||||
<span class="icon"><i class="fas fa-copy" /></span>
|
||||
@click="() => copyText(filterItems.map(i => i.text).join('\n'))">
|
||||
<span class="icon"><i class="fas fa-copy"/></span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
@@ -67,35 +67,36 @@
|
||||
<div class="column is-12">
|
||||
<div class="logbox is-grid" ref="logContainer" v-if="!error" @scroll.passive="handleScroll">
|
||||
<code id="logView" class="p-1 logline is-block" :class="{ 'is-pre-wrap': wrapLines, 'is-pre': !wrapLines }">
|
||||
<span class="is-block m-0 notification is-info is-dark has-text-centered" v-if="reachedEnd && !query">
|
||||
<span class="notification-title">
|
||||
<span class="icon"><i class="fas fa-exclamation-triangle" /></span>
|
||||
No more logs available for this file.
|
||||
</span>
|
||||
</span>
|
||||
<span v-for="item in filterItems" :key="item.id" class="is-block">
|
||||
<span v-if="item.date">[<span class="has-tooltip" :title="item.date">{{ formatDate(item.date)
|
||||
}}</span>]: </span>
|
||||
<span v-if="item?.item_id"><span class="is-clickable has-tooltip" @click="goto_history_item(item)"><span
|
||||
class="icon"><i class="fas fa-history" /></span><span>View</span></span> </span>
|
||||
<span>{{ item.text }}</span>
|
||||
</span>
|
||||
<span class="is-block" v-if="filterItems.length < 1">
|
||||
<span class="is-block m-0 notification is-warning is-dark has-text-centered" v-if="query">
|
||||
<span class="notification-title is-danger">
|
||||
<span class="icon"><i class="fas fa-filter" /></span>
|
||||
No logs match this query: <u>{{ query }}</u>
|
||||
</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span class="has-text-danger">No logs available</span></span>
|
||||
</span>
|
||||
</code>
|
||||
<span class="is-block m-0 notification is-info is-dark has-text-centered" v-if="reachedEnd && !query">
|
||||
<span class="notification-title">
|
||||
<span class="icon"><i class="fas fa-exclamation-triangle"/></span>
|
||||
No more logs available for this file.
|
||||
</span>
|
||||
</span>
|
||||
<span v-for="item in filterItems" :key="item.id" class="is-block">
|
||||
<span v-if="item.date">[<span class="has-tooltip" :title="item.date">{{
|
||||
formatDate(item.date)
|
||||
}}</span>]: </span>
|
||||
<span v-if="item?.item_id"><span class="is-clickable has-tooltip" @click="goto_history_item(item)"><span
|
||||
class="icon"><i class="fas fa-history"/></span><span>View</span></span> </span>
|
||||
<span>{{ item.text }}</span>
|
||||
</span>
|
||||
<span class="is-block" v-if="filterItems.length < 1">
|
||||
<span class="is-block m-0 notification is-warning is-dark has-text-centered" v-if="query">
|
||||
<span class="notification-title is-danger">
|
||||
<span class="icon"><i class="fas fa-filter"/></span>
|
||||
No logs match this query: <u>{{ query }}</u>
|
||||
</span>
|
||||
</span>
|
||||
<span v-else>
|
||||
<span class="has-text-danger">No logs available</span></span>
|
||||
</span>
|
||||
</code>
|
||||
<div ref="bottomMarker"></div>
|
||||
</div>
|
||||
|
||||
<Message v-if="error" title="API Error" message_class="has-background-warning-90 has-text-dark" :message="error"
|
||||
:use-close="true" @close="router.push('/logs')" />
|
||||
:use-close="true" @close="router.push('/logs')"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,11 +109,11 @@
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#logView>span:nth-child(even) {
|
||||
#logView > span:nth-child(even) {
|
||||
color: #ffc9d4;
|
||||
}
|
||||
|
||||
#logView>span:nth-child(odd) {
|
||||
#logView > span:nth-child(odd) {
|
||||
color: #e3c981;
|
||||
}
|
||||
|
||||
@@ -144,15 +145,15 @@ div.logbox pre {
|
||||
<script setup>
|
||||
import Message from '~/components/Message'
|
||||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { disableOpacity, enableOpacity, goto_history_item, notification, parse_api_response } from '~/utils/index'
|
||||
import {useStorage} from '@vueuse/core'
|
||||
import {disableOpacity, enableOpacity, goto_history_item, notification, parse_api_response} from '~/utils/index'
|
||||
import request from '~/utils/request'
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import {fetchEventSource} from '@microsoft/fetch-event-source'
|
||||
|
||||
const router = useRouter()
|
||||
const filename = useRoute().params.filename
|
||||
|
||||
useHead({ title: `Logs : ${filename}` })
|
||||
useHead({title: `Logs : ${filename}`})
|
||||
|
||||
const query = ref()
|
||||
const data = ref([])
|
||||
@@ -233,7 +234,7 @@ const loadContent = async () => {
|
||||
// Auto-scroll only if the user was already at the bottom
|
||||
await nextTick(() => {
|
||||
if (autoScroll.value && bottomMarker.value) {
|
||||
bottomMarker.value.scrollIntoView({ behavior: 'auto' })
|
||||
bottomMarker.value.scrollIntoView({behavior: 'auto'})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -273,7 +274,7 @@ const scrollToBottom = () => {
|
||||
autoScroll.value = true
|
||||
nextTick(() => {
|
||||
if (bottomMarker.value) {
|
||||
bottomMarker.value.scrollIntoView({ behavior: 'smooth' })
|
||||
bottomMarker.value.scrollIntoView({behavior: 'smooth'})
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -315,7 +316,7 @@ const watchLog = () => {
|
||||
|
||||
await nextTick(() => {
|
||||
if (autoScroll.value && bottomMarker.value) {
|
||||
bottomMarker.value.scrollIntoView({ behavior: 'smooth' })
|
||||
bottomMarker.value.scrollIntoView({behavior: 'smooth'})
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
@@ -332,11 +333,7 @@ const watchLog = () => {
|
||||
|
||||
const closeStream = () => {
|
||||
if (stream.value) {
|
||||
try {
|
||||
stream.value.close()
|
||||
ctrl.abort()
|
||||
} catch (e) {
|
||||
}
|
||||
ctrl.abort()
|
||||
stream.value = null
|
||||
}
|
||||
}
|
||||
@@ -375,7 +372,7 @@ const deleteFile = async () => {
|
||||
try {
|
||||
closeStream();
|
||||
|
||||
const response = await request(`/log/${filename}`, { method: 'DELETE' })
|
||||
const response = await request(`/log/${filename}`, {method: 'DELETE'})
|
||||
|
||||
if (response.ok) {
|
||||
notification('success', 'Information', `Logfile '${filename}' has been deleted.`)
|
||||
@@ -390,7 +387,7 @@ const deleteFile = async () => {
|
||||
json = await response.json()
|
||||
} catch (e) {
|
||||
json = {
|
||||
error: { code: response.status, message: response.statusText }
|
||||
error: {code: response.status, message: response.statusText}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user