Skip to content

Commit

Permalink
add log preview
Browse files Browse the repository at this point in the history
  • Loading branch information
zyxkad committed Jun 6, 2024
1 parent b25a8a0 commit f87833e
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 27 deletions.
19 changes: 18 additions & 1 deletion dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package main

import (
"bytes"
"compress/gzip"
"embed"
"encoding/json"
"io"
Expand All @@ -29,6 +30,8 @@ import (
"net/http"
"path"
"strings"

"github.com/LiterMC/go-openbmclapi/utils"
)

//go:generate npm -C dashboard ci
Expand Down Expand Up @@ -63,6 +66,7 @@ func (cr *Cluster) serveDashboard(rw http.ResponseWriter, req *http.Request, pth
http.Error(rw, "405 Method Not Allowed", http.StatusMethodNotAllowed)
return
}
acceptEncoding := utils.SplitCSV(req.Header.Get("Accept-Encoding"))
switch pth {
case "":
break
Expand All @@ -84,11 +88,13 @@ func (cr *Cluster) serveDashboard(rw http.ResponseWriter, req *http.Request, pth
fd, err := dsbDist.Open(pth)
if err == nil {
defer fd.Close()
if stat, err := fd.Stat(); err != nil || stat.IsDir() {
stat, err := fd.Stat()
if err != nil || stat.IsDir() {
http.NotFound(rw, req)
return
}
name := path.Base(pth)
size := stat.Size()
typ := mime.TypeByExtension(path.Ext(name))
if typ == "" {
typ = "application/octet-stream"
Expand All @@ -99,6 +105,17 @@ func (cr *Cluster) serveDashboard(rw http.ResponseWriter, req *http.Request, pth
rw.Header().Set("Cache-Control", "public, max-age=2592000")
}
}
if acceptEncoding["gzip"] != 0 && size > 1024 {
buf := bytes.NewBuffer(nil)
gw := gzip.NewWriter(buf)
if _, err := io.Copy(gw, fd); err == nil {
if err = gw.Close(); err == nil {
rw.Header().Set("Content-Encoding", "gzip")
http.ServeContent(rw, req, name, startTime, bytes.NewReader(buf.Bytes()))
return
}
}
}
http.ServeContent(rw, req, name, startTime, fd.(io.ReadSeeker))
return
}
Expand Down
8 changes: 7 additions & 1 deletion dashboard/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const selectedLang = computed({
height: 4rem;
padding-left: 2rem;
padding-right: 1rem;
background-color: color-mix(in srgb, var(--primary-50), transparent);
background-color: color-mix(in srgb, var(--primary-50) 30%, transparent);
box-shadow: #0008 0 0 1rem -0.5rem;
backdrop-filter: blur(0.4rem);
}
Expand Down Expand Up @@ -214,6 +214,12 @@ const selectedLang = computed({
text-decoration: underline;
}
@media (prefers-color-scheme: dark) {
#header {
background-color: color-mix(in srgb, var(--primary-50) 10%, transparent);
}
}
@media (max-width: 60rem) {
#header {
padding: 0;
Expand Down
14 changes: 11 additions & 3 deletions dashboard/src/api/v0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export async function getChallenge(action: string): Promise<string> {
}

export function signChallenge(challenge: string, secret: string): string {
const signed = sha256.hmac(sha256(secret), challenge);
const signed = sha256.hmac(sha256(secret), challenge)
return signed
}

Expand Down Expand Up @@ -412,7 +412,11 @@ export async function getLogFiles(token: string): Promise<FileInfo[]> {
return res.data.files
}

export async function getLogFile(token: string, name: string, noEncrypt?: boolean): Promise<ArrayBuffer> {
export async function getLogFile(
token: string,
name: string,
noEncrypt?: boolean,
): Promise<ArrayBuffer> {
const LOGFILE_URL = `${window.location.origin}/api/v0/log_file/`
const u = new URL(name.startsWith('/') ? name.substr(1) : name, LOGFILE_URL)
if (noEncrypt) {
Expand All @@ -427,7 +431,11 @@ export async function getLogFile(token: string, name: string, noEncrypt?: boolea
return res.data
}

export async function getLogFileURL(token: string, name: string, noEncrypt?: boolean): Promise<string> {
export async function getLogFileURL(
token: string,
name: string,
noEncrypt?: boolean,
): Promise<string> {
const LOGFILE_URL = `${window.location.origin}/api/v0/log_file/`
const u = new URL(name.startsWith('/') ? name.substr(1) : name, LOGFILE_URL)
if (noEncrypt) {
Expand Down
7 changes: 7 additions & 0 deletions dashboard/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

:root {
font-size: 16px;
--dialog-width: 60rem;
}

body {
Expand All @@ -19,3 +20,9 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

@media (max-width: 60rem) {
:root {
--dialog-width: 100vw;
}
}
2 changes: 2 additions & 0 deletions dashboard/src/assets/theme.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@import 'primevue/resources/themes/lara-light-green/theme.css' not (prefers-color-scheme: dark);
@import 'primevue/resources/themes/lara-dark-green/theme.css' (prefers-color-scheme: dark);
25 changes: 25 additions & 0 deletions dashboard/src/components/FileContentCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script setup lang="ts">
import Button from 'primevue/button'
import type { FileInfo } from '@/api/v0'
defineProps<{
info: FileInfo
content: string
}>()
</script>
<template>
<div>
<slot :info="info" :content="content" />
<pre class="code-box"><code>{{ content }}</code></pre>
</div>
</template>
<style scoped>
.code-box {
width: 100%;
height: 62vh;
padding: 0.4rem;
border-radius: 0.4rem;
background: var(--surface-c);
overflow: auto;
}
</style>
12 changes: 5 additions & 7 deletions dashboard/src/components/FileListCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ defineProps<{
}>()
defineEmits<{
(e: 'click', file: FileInfo, index: number): void,
(e: 'click', file: FileInfo, index: number): void
}>()
</script>
<template>
<Card>
Expand All @@ -27,11 +26,11 @@ defineEmits<{
<template v-else>
<div class="file-list-box">
<div v-for="(file, i) in files" v-key="file.name" class="file-elem">
<div class="file-name" @click="$emit('click', file, i)">
{{file.name}}
<div class="file-name flex-row-center" @click="$emit('click', file, i)">
{{ file.name }}
</div>
<div class="flex-row-center">
<div class="file-size">{{formatBytes(file.size)}}</div>
<div class="file-size">{{ formatBytes(file.size) }}</div>
<slot :index="i" :file="file" />
</div>
</div>
Expand Down Expand Up @@ -75,5 +74,4 @@ defineEmits<{
font-weight: 200;
user-select: none;
}
</style>
</style>
2 changes: 1 addition & 1 deletion dashboard/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { useCookies, bindRefToCookie } from './cookies'
import './utils/chart'
import { ping } from '@/api/v0'

import 'primevue/resources/themes/lara-light-green/theme.css'
import './assets/theme.css'
import 'primeicons/primeicons.css'
import './assets/main.css'

Expand Down
63 changes: 49 additions & 14 deletions dashboard/src/views/LogListView.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
<script setup lang="ts">
import { ref, inject, onMounted, type Ref } from 'vue'
import Button from 'primevue/button'
import Dialog from 'primevue/dialog'
import { useToast } from 'primevue/usetoast'
import FileListCard from '@/components/FileListCard.vue'
import {
getLogFiles,
getLogFile,
getLogFileURL,
type FileInfo,
} from '@/api/v0'
import FileContentCard from '@/components/FileContentCard.vue'
import { getLogFiles, getLogFile, getLogFileURL, type FileInfo } from '@/api/v0'
import { tr } from '@/lang'
const toast = useToast()
Expand All @@ -22,39 +19,77 @@ async function refreshFileList(): Promise<void> {
logList.value = await getLogFiles(token.value)
}
const showInfo = ref<FileInfo | null>(null)
const fileContent = ref<string | null>(null)
async function openFile(file: FileInfo): Promise<void> {
if (!token.value) {
return
}
const buf = await getLogFile(token.value, file.name, true)
console.log(buf)
alert('TODO: display log file content')
showInfo.value = file
fileContent.value = new TextDecoder().decode(new Uint8Array(buf))
}
async function downloadLogFile(file: FileInfo): Promise<void> {
async function downloadLogFile(file: FileInfo, noEncrypt?: boolean): Promise<void> {
if (!token.value) {
return
}
const u = await getLogFileURL(token.value, file.name, false)
const u = await getLogFileURL(token.value, file.name, noEncrypt)
window.open(u)
}
onMounted(() => {
refreshFileList()
})
</script>
<template>
<div>
<h1>
{{ tr('title.logs') }}
</h1>
<FileListCard class="filelist-card" :name="'logs/'" :files="logList" v-slot="{ index, file }" @click="openFile">
<Button icon="pi pi-file-export" @click="downloadLogFile(file)"/>
<FileListCard
class="filelist-card"
:name="'logs/'"
:files="logList"
v-slot="{ index, file }"
@click="openFile"
>
<Button icon="pi pi-file-export" aria-label="Export log" @click="downloadLogFile(file)" />
</FileListCard>
<Dialog
:visible="!!showInfo"
@update:visible="(show) => !show && (showInfo = null)"
modal
:header="(showInfo && showInfo.name) || undefined"
:style="{ width: 'var(--dialog-width)' }"
>
<FileContentCard
v-if="showInfo && fileContent"
:info="showInfo"
:content="fileContent"
v-slot="{ info }"
>
<div class="flex-row-center tool-box">
<Button
icon="pi pi-download"
aria-label="Download"
link
@click="downloadLogFile(info, true)"
/>
<Button icon="pi pi-file-export" label="Export" @click="downloadLogFile(info)" />
</div>
</FileContentCard>
</Dialog>
</div>
</template>
<style scoped>
.filelist-card {
.tool-box {
width: 100%;
margin-top: 0.3rem;
}
.tool-box > * {
margin-left: 0.3rem;
}
</style>

0 comments on commit f87833e

Please sign in to comment.