205 lines
5.1 KiB
Vue
205 lines
5.1 KiB
Vue
<template>
|
|
<MainLayout>
|
|
<template #dialogs>
|
|
</template>
|
|
<template #header>
|
|
<TopBar class="navbar">
|
|
<template #left>
|
|
<span class="title">{{ name }}</span>
|
|
</template>
|
|
<template #right>
|
|
<Button type="button" :label="$t('logs.clear')" icon="pi pi-eraser" @click="onClear()" style="margin-right: 5px" />
|
|
<a style="margin-right: 25px;" :href="downloadUrl" target="_blank"><Button :label="$t('logs.download')" icon="pi pi-download" /></a>
|
|
<a style="margin-right: 5px;" :href="'/frontend/filemanager.html#/home/app/' + id" target="_blank" v-show="type === 'app'"><Button type="button" severity="secondary" icon="pi pi-folder" :label="$t('filemanager.title')" /></a>
|
|
<a style="margin-right: 5px;" :href="'/terminal.html?id=' + id" target="_blank" v-show="type === 'app'"><Button severity="secondary" icon="pi pi-desktop" :label="$t('terminal.title')" /></a>
|
|
<Button type="button" :label="$t('filemanager.toolbar.restartApp')" severity="secondary" icon="pi pi-sync" @click="onRestartApp" :loading="busyRestart" v-show="type === 'app'"/>
|
|
</template>
|
|
</TopBar>
|
|
</template>
|
|
<template #body>
|
|
<div v-for="line in logLines" :key="line.id" class="log-line">
|
|
<span class="time">{{ line.time }}</span><span v-html="line.html"></span>
|
|
</div>
|
|
<div ref="scrollAnchor" class="bottom-spacer"></div>
|
|
</template>
|
|
</MainLayout>
|
|
</template>
|
|
|
|
<script>
|
|
|
|
import Button from 'primevue/button';
|
|
import Dialog from 'primevue/dialog';
|
|
import InputText from 'primevue/inputtext';
|
|
import Menu from 'primevue/menu';
|
|
import ProgressSpinner from 'primevue/progressspinner';
|
|
|
|
import { TopBar, MainLayout } from 'pankow';
|
|
|
|
import { create } from '../models/LogsModel.js';
|
|
|
|
const API_ORIGIN = import.meta.env.VITE_API_ORIGIN ? 'https://' + import.meta.env.VITE_API_ORIGIN : '';
|
|
|
|
export default {
|
|
name: 'LogsViewer',
|
|
components: {
|
|
Button,
|
|
Dialog,
|
|
InputText,
|
|
MainLayout,
|
|
Menu,
|
|
ProgressSpinner,
|
|
TopBar
|
|
},
|
|
data() {
|
|
return {
|
|
accessToken: localStorage.token,
|
|
apiOrigin: API_ORIGIN || '',
|
|
logsModel: null,
|
|
busyRestart: false,
|
|
id: '',
|
|
name: '',
|
|
type: '',
|
|
downloadUrl: '',
|
|
logLines: []
|
|
};
|
|
},
|
|
methods: {
|
|
onClear() {
|
|
this.logLines = [];
|
|
},
|
|
onDownload() {
|
|
this.logsModel.download();
|
|
},
|
|
async onRestartApp() {
|
|
if (this.type !== 'app') return;
|
|
|
|
this.busyRestart = true;
|
|
|
|
await this.logsModel.restartApp();
|
|
|
|
this.busyRestart = false;
|
|
}
|
|
},
|
|
async mounted() {
|
|
if (!localStorage.token) {
|
|
console.error('Set localStorage.token');
|
|
return;
|
|
}
|
|
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const appId = urlParams.get('appId');
|
|
const taskId = urlParams.get('taskId');
|
|
const crashId = urlParams.get('crashId');
|
|
const id = urlParams.get('id');
|
|
|
|
if (appId) {
|
|
this.type = 'app';
|
|
this.id = appId;
|
|
this.name = 'App ' + appId;
|
|
} else if (taskId) {
|
|
this.type = 'task';
|
|
this.id = taskId;
|
|
this.name = 'Task ' + taskId;
|
|
} else if (crashId) {
|
|
this.type = 'crash';
|
|
this.id = crashId;
|
|
this.name = 'Crash ' + crashId;
|
|
} else if (id) {
|
|
if (id === 'box') {
|
|
this.type = 'platform';
|
|
this.id = id;
|
|
this.name = 'Box';
|
|
} else {
|
|
this.type = 'service';
|
|
this.id = id;
|
|
this.name = 'Service ' + id;
|
|
}
|
|
} else {
|
|
console.error('no supported log type specified');
|
|
return;
|
|
}
|
|
|
|
this.logsModel = create(this.apiOrigin, this.accessToken, this.type, this.id);
|
|
|
|
if (this.type === 'app') {
|
|
try {
|
|
const app = await this.logsModel.getApp();
|
|
this.name = `${app.label || app.fqdn} (${app.manifest.title})`;
|
|
} catch (e) {
|
|
console.error(`Failed to get app info for ${this.id}:`, e);
|
|
}
|
|
}
|
|
|
|
window.document.title = `Logs Viewer - ${this.name}`;
|
|
|
|
this.downloadUrl = this.logsModel.getDownloadUrl();
|
|
|
|
this.logsModel.stream((id, time, html) => {
|
|
this.logLines.push({ id, time, html});
|
|
|
|
const tmp = document.getElementsByClassName('cloudron-layout-body')[0];
|
|
if (!tmp) return;
|
|
|
|
const autoScroll = tmp.scrollTop > (tmp.scrollHeight - tmp.clientHeight - 34);
|
|
if (autoScroll) setTimeout(() => this.$refs.scrollAnchor.scrollIntoView(false), 1);
|
|
}, function (error) {
|
|
console.error('Failed to start log stream:', error);
|
|
})
|
|
}
|
|
};
|
|
|
|
</script>
|
|
|
|
<style>
|
|
|
|
body {
|
|
background-color: black;
|
|
}
|
|
|
|
.title {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.cloudron-layout-body {
|
|
cursor: text;
|
|
}
|
|
|
|
.cloudron-top {
|
|
background-color: black;
|
|
color: white;
|
|
margin-bottom: 0 !important;
|
|
padding: 5px 10px;
|
|
}
|
|
|
|
.log-line {
|
|
color: white;
|
|
font-family: monospace;
|
|
font-size: 14px;
|
|
line-height: 1.2;
|
|
white-space: nowrap;
|
|
width: 100%;
|
|
}
|
|
|
|
.log-line:hover {
|
|
background-color: #333333;
|
|
}
|
|
|
|
.log-line > .time {
|
|
color: #0ff;
|
|
position: sticky;
|
|
left: 0;
|
|
margin-right: 10px;
|
|
background-color: black;
|
|
}
|
|
|
|
.log-line:hover > .time {
|
|
background-color: #333333;
|
|
}
|
|
|
|
.bottom-spacer {
|
|
height: 5px;
|
|
}
|
|
|
|
</style>
|
|
|