238 lines
6.2 KiB
Vue
238 lines
6.2 KiB
Vue
<script setup>
|
|
|
|
import { useI18n } from 'vue-i18n';
|
|
const i18n = useI18n();
|
|
const t = i18n.t;
|
|
|
|
import { ref, useTemplateRef, onUnmounted, onMounted } from 'vue';
|
|
import { Button, InputDialog, TopBar, MainLayout, ButtonGroup } from '@cloudron/pankow';
|
|
import LogsModel from '../models/LogsModel.js';
|
|
import AppsModel from '../models/AppsModel.js';
|
|
|
|
const linesContainer = useTemplateRef('linesContainer');
|
|
const inputDialog = useTemplateRef('inputDialog');
|
|
|
|
let logsModel = null;
|
|
const appsModel = AppsModel.create();
|
|
let refreshInterval = 0;
|
|
|
|
const busyRestart = ref(false);
|
|
const showRestart = ref(false);
|
|
const showFilemanager = ref(false);
|
|
const showTerminal = ref(false);
|
|
const id = ref('');
|
|
const name = ref('');
|
|
const type = ref('');
|
|
const downloadUrl = ref('');
|
|
|
|
function onClear() {
|
|
while (linesContainer.value.firstChild) linesContainer.value.removeChild(linesContainer.value.firstChild);
|
|
}
|
|
|
|
async function onRestartApp() {
|
|
if (type.value !== 'app') return;
|
|
|
|
const confirmed = await inputDialog.value.confirm({
|
|
message: t('filemanager.toolbar.restartApp') + '?',
|
|
confirmLabel: t('main.action.restart'),
|
|
confirmStyle: 'danger',
|
|
rejectLabel: t('main.dialog.cancel'),
|
|
rejectStyle: 'secondary',
|
|
});
|
|
|
|
if (!confirmed) return;
|
|
|
|
busyRestart.value = true;
|
|
|
|
const [error] = await appsModel.restart(id.value);
|
|
if (error) return console.error(error);
|
|
|
|
busyRestart.value = false;
|
|
}
|
|
|
|
onMounted(async () => {
|
|
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 idParam = urlParams.get('id');
|
|
|
|
if (appId && taskId) {
|
|
type.value = 'task';
|
|
id.value = taskId;
|
|
name.value = 'Task ' + taskId;
|
|
} else if (appId) {
|
|
type.value = 'app';
|
|
id.value = appId;
|
|
name.value = 'App ' + appId;
|
|
} else if (taskId) {
|
|
type.value = 'task';
|
|
id.value = taskId;
|
|
name.value = 'Task ' + taskId;
|
|
} else if (crashId) {
|
|
type.value = 'crash';
|
|
id.value = crashId;
|
|
name.value = 'Crash ' + crashId;
|
|
} else if (idParam) {
|
|
if (idParam === 'box') {
|
|
type.value = 'platform';
|
|
id.value = idParam;
|
|
name.value = 'Box';
|
|
} else {
|
|
type.value = 'service';
|
|
id.value = idParam;
|
|
name.value = 'Service ' + idParam;
|
|
}
|
|
} else {
|
|
console.error('no supported log type specified');
|
|
return;
|
|
}
|
|
|
|
logsModel = LogsModel.create(type.value, id.value, { appId });
|
|
|
|
if (type.value === 'app') {
|
|
const [error, app] = await appsModel.get(id.value);
|
|
if (error) return console.error(error);
|
|
|
|
name.value = `${app.label || app.fqdn} (${app.manifest.title})`;
|
|
showFilemanager.value = !!app.manifest.addons.localstorage;
|
|
showTerminal.value = app.manifest.id !== 'io.cloudron.builtin.appproxy';
|
|
showRestart.value = app.manifest.id !== 'io.cloudron.builtin.appproxy';
|
|
}
|
|
|
|
window.document.title = `Logs Viewer - ${name.value}`;
|
|
|
|
downloadUrl.value = logsModel.getDownloadUrl();
|
|
|
|
const maxLines = 1000;
|
|
let lines = 0;
|
|
let newLogLines = [];
|
|
|
|
const tmp = document.getElementsByClassName('pankow-main-layout-body')[0];
|
|
refreshInterval = setInterval(() => {
|
|
newLogLines = newLogLines.slice(-maxLines);
|
|
|
|
for (const line of newLogLines) {
|
|
if (lines < maxLines) ++lines;
|
|
else if (linesContainer.value.firstChild) linesContainer.value.removeChild(linesContainer.value.firstChild);
|
|
|
|
const logLine = document.createElement('div');
|
|
logLine.className = 'log-line';
|
|
logLine.innerHTML = `<span class="time">${line.time || '[no timestamp] ' }</span> <span>${line.html}</span>`;
|
|
linesContainer.value.appendChild(logLine);
|
|
|
|
const autoScroll = tmp.scrollTop > (tmp.scrollHeight - tmp.clientHeight - 34);
|
|
if (autoScroll) setTimeout(() => tmp.scrollTop = tmp.scrollHeight, 1);
|
|
}
|
|
|
|
newLogLines = [];
|
|
}, 500);
|
|
|
|
logsModel.stream((time, html) => {
|
|
newLogLines.push({ time, html });
|
|
}, function (error) {
|
|
newLogLines.push({ time: error.time, html: error.html });
|
|
});
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
clearInterval(refreshInterval);
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<MainLayout>
|
|
<template #dialogs>
|
|
<InputDialog ref="inputDialog" />
|
|
</template>
|
|
<template #header>
|
|
<TopBar class="navbar">
|
|
<template #left>
|
|
<span class="title">{{ name }}</span>
|
|
</template>
|
|
<template #right>
|
|
<ButtonGroup>
|
|
<Button icon="fa-solid fa-eraser" @click="onClear()">{{ $t('logs.clear') }}</Button>
|
|
<Button :href="downloadUrl" target="_blank" icon="fa-solid fa-download">{{ $t('logs.download') }}</Button>
|
|
</ButtonGroup>
|
|
|
|
<Button style="margin: 0 20px;" v-tooltip="$t('filemanager.toolbar.restartApp')" v-show="showRestart" secondary tool :loading="busyRestart" icon="fa-solid fa-arrows-rotate" @click="onRestartApp"/>
|
|
|
|
<ButtonGroup>
|
|
<Button :href="'/terminal.html?id=' + id" target="_blank" v-show="showTerminal" secondary tool icon="fa-solid fa-terminal" v-tooltip="$t('terminal.title')" />
|
|
<Button :href="'/filemanager.html#/home/app/' + id" target="_blank" v-show="showFilemanager" secondary tool icon="fa-solid fa-folder" v-tooltip="$t('filemanager.title')" />
|
|
</ButtonGroup>
|
|
</template>
|
|
</TopBar>
|
|
</template>
|
|
<template #body>
|
|
<div ref="linesContainer"></div>
|
|
<div class="bottom-spacer"></div>
|
|
</template>
|
|
</MainLayout>
|
|
</template>
|
|
|
|
<style>
|
|
|
|
body {
|
|
background-color: black !important;
|
|
}
|
|
|
|
.title {
|
|
font-size: 20px;
|
|
}
|
|
|
|
.pankow-main-layout-body {
|
|
cursor: text;
|
|
}
|
|
|
|
@media (max-width: 641px) {
|
|
.hide-phone {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
.pankow-top-bar {
|
|
background-color: black;
|
|
color: white;
|
|
margin-bottom: 0 !important;
|
|
padding: 5px 10px;
|
|
}
|
|
|
|
.log-line {
|
|
color: white;
|
|
font-family: monospace;
|
|
font-size: 14px;
|
|
white-space: pre-wrap;
|
|
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>
|
|
|