Move filemanager/ to frontend/
This commit is contained in:
190
frontend/src/components/LogsViewer.vue
Normal file
190
frontend/src/components/LogsViewer.vue
Normal file
@@ -0,0 +1,190 @@
|
||||
<template>
|
||||
<MainLayout>
|
||||
<template #dialogs>
|
||||
</template>
|
||||
<template #header>
|
||||
<TopBar class="navbar">
|
||||
<template #left>
|
||||
{{ name }}
|
||||
</template>
|
||||
<template #right>
|
||||
<a class="p-button p-button-primary" style="margin-right: 5px;" :href="'/terminal.html?id=' + id" target="_blank" v-show="type === 'app'"><span class="p-button-icon p-button-icon-left pi pi-desktop"></span> {{ $t('terminal.title') }}</a>
|
||||
<a class="p-button p-button-primary" style="margin-right: 5px;" :href="'/frontend/filemanager.html#/home/app/' + id" target="_blank" v-show="type === 'app'"><span class="p-button-icon p-button-icon-left pi pi-folder"></span> {{ $t('filemanager.title') }}</a>
|
||||
<Button type="button" :label="$t('logs.clear')" icon="pi pi-eraser" @click="onClear()" style="margin-right: 5px" />
|
||||
<a class="p-button p-button-primary" style="margin-right: 5px;" :href="downloadUrl" target="_blank"><span class="p-button-icon p-button-icon-left pi pi-download"></span> {{ $t('logs.download') }}</a>
|
||||
</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, BottomBar, 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,
|
||||
id: '',
|
||||
name: '',
|
||||
type: '',
|
||||
downloadUrl: '',
|
||||
logLines: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onClear() {
|
||||
this.logLines = [];
|
||||
},
|
||||
onDownload() {
|
||||
this.logsModel.download();
|
||||
}
|
||||
},
|
||||
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.fqdn + ' (' + app.manifest.title + ')';
|
||||
} catch (e) {
|
||||
console.error(`Failed to get app info for ${this.id}:`, e);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.cloudron-layout-body {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.cloudron-top {
|
||||
background-color: black;
|
||||
color: white;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.bottom-spacer {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
a.p-button:hover {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
background: #0d89ec;
|
||||
border-color: #0d89ec;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
50
frontend/src/components/PreviewPanel.vue
Normal file
50
frontend/src/components/PreviewPanel.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div class="preview-panel">
|
||||
<img :src="item.previewUrl || item.icon" :alt="item.name" :class="{'shadow': item.previewUrl }" @error="iconError($event)"/>
|
||||
<p>{{ item.name }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PreviewPanel',
|
||||
props: {
|
||||
item: Object,
|
||||
fallbackIcon: String
|
||||
},
|
||||
methods: {
|
||||
iconError(event) {
|
||||
event.target.src = this.fallbackIcon;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.preview-panel {
|
||||
padding: 0 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.preview-panel > img {
|
||||
min-width: 90%;
|
||||
max-width: 256px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,.2);
|
||||
}
|
||||
|
||||
.preview-panel > p {
|
||||
text-align: center;
|
||||
font-size: 24px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
</style>
|
||||
Reference in New Issue
Block a user