Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client log view improvements #1385

Merged
merged 16 commits into from
Aug 31, 2024
6 changes: 6 additions & 0 deletions photon-client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions photon-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"three": "^0.160.0",
"vue": "^2.7.14",
"vue-router": "^3.6.5",
"vue-virtual-scroll-list": "^2.3.5",
"vuetify": "^2.7.1"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion photon-client/src/components/app/photon-camera-stream.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ onBeforeUnmount(() => {

<template>
<div class="stream-container">
<img :id="id" crossorigin="anonymous" :src="streamSrc" :alt="streamDesc" :style="streamStyle" ref="mjpgStream" />
<img :id="id" ref="mjpgStream" crossorigin="anonymous" :src="streamSrc" :alt="streamDesc" :style="streamStyle" />
<div class="stream-overlay" :style="overlayStyle">
<pv-icon
icon-name="mdi-camera-image"
Expand Down
24 changes: 24 additions & 0 deletions photon-client/src/components/app/photon-log-entry.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup lang="ts">
import { LogLevel, type LogMessage } from "@/types/SettingTypes";
import { computed } from "vue";

const props = defineProps<{ source: LogMessage }>();

const logColorClass = computed<string>(() => {
switch (props.source.level) {
case LogLevel.ERROR:
return "red--text";
case LogLevel.WARN:
return "yellow--text";
case LogLevel.INFO:
return "light-blue--text";
case LogLevel.DEBUG:
return "white--text";
}
return "";
});
</script>

<template>
<div :class="logColorClass">[{{ source.timestamp.toTimeString().split(" ")[0] }}] {{ source.message }}</div>
</template>
214 changes: 145 additions & 69 deletions photon-client/src/components/app/photon-log-view.vue
Original file line number Diff line number Diff line change
@@ -1,40 +1,68 @@
<script setup lang="ts">
import { computed, inject, ref } from "vue";
import { computed, inject, ref, watch } from "vue";
import { LogLevel, type LogMessage } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
import LogEntry from "@/components/app/photon-log-entry.vue";
import VirtualList from "vue-virtual-scroll-list";

const selectedLogLevels = ref<LogLevel[]>([LogLevel.ERROR, LogLevel.WARN, LogLevel.INFO]);
const backendHost = inject<string>("backendHost");

const searchQuery = ref("");
const timeInput = ref<string>();
const autoScroll = ref(true);
const logList = ref();
const logKeeps = ref(40);
const exportLogFile = ref();
const selectedLogLevels = ref({
[LogLevel.ERROR]: true,
[LogLevel.WARN]: true,
[LogLevel.INFO]: true,
[LogLevel.DEBUG]: false
});

const logs = computed<LogMessage[]>(() =>
useStateStore().logMessages.filter((message) => selectedLogLevels.value.includes(message.level))
useStateStore()
.logMessages.filter(
(message) =>
selectedLogLevels.value[message.level] &&
message.message.toLowerCase().includes(searchQuery.value?.toLowerCase() || "") &&
(timeInput.value === undefined ||
message.timestamp >=
new Date().setHours(
parseInt(timeInput.value.substring(0, 2)),
parseInt(timeInput.value.substring(3, 5)),
parseInt(timeInput.value.substring(6, 8))
))
)
.map((item, index) => ({ ...item, index: index }))
);

const backendHost = inject<string>("backendHost");
watch(logs, () => {
if (!logList.value) return;

const getLogColor = (level: LogLevel): string => {
switch (level) {
case LogLevel.ERROR:
return "red";
case LogLevel.WARN:
return "yellow";
case LogLevel.INFO:
return "green";
case LogLevel.DEBUG:
return "white";
}
return "";
};
// Dynamic list render size based on console size
logKeeps.value = Math.ceil(logList.value.$el.clientHeight / 17.5) + 20;

const bottomOffset = Math.abs(
logList.value.$el.scrollHeight - logList.value.$el.scrollTop - logList.value.$el.clientHeight
);
autoScroll.value = bottomOffset < 50;

if (autoScroll.value) logList.value.scrollToBottom();
});

const getLogLevelFromIndex = (index: number): string => {
return LogLevel[index];
};

const exportLogFile = ref();

const handleLogExport = () => {
exportLogFile.value.click();
};

const handleLogClear = () => {
useStateStore().logMessages = [];
};

document.addEventListener("keydown", (e) => {
switch (e.key) {
case "`":
Expand All @@ -46,20 +74,16 @@ document.addEventListener("keydown", (e) => {

<template>
<v-dialog v-model="useStateStore().showLogModal" width="1500" dark>
<v-card dark class="pt-3" color="primary" flat>
<v-row class="heading-container pl-6 pr-6">
<v-col>
<v-card-title>View Program Logs</v-card-title>
<v-card dark class="dialog-container pa-6" color="primary" flat>
<!-- Logs header -->
<v-row class="no-gutters pb-3">
<v-col cols="4">
<v-card-title class="pa-0">Program Logs</v-card-title>
</v-col>
<v-col class="align-self-center">
<v-btn
color="secondary"
style="margin-left: auto; max-width: 500px; width: 100%"
depressed
@click="handleLogExport"
>
<v-icon left class="open-icon"> mdi-download </v-icon>
<span class="open-label">Download Current Log</span>
<v-col class="align-self-center pl-3" style="text-align: right">
<v-btn text color="white" @click="handleLogExport">
<v-icon left class="menu-icon"> mdi-download </v-icon>
<span class="menu-label">Download</span>

<!-- Special hidden link that gets 'clicked' when the user exports journalctl logs -->
<a
Expand All @@ -70,58 +94,110 @@ document.addEventListener("keydown", (e) => {
target="_blank"
/>
</v-btn>
<v-btn text color="white" @click="handleLogClear">
<v-icon left class="menu-icon"> mdi-trash-can-outline </v-icon>
<span class="menu-label">Clear Client Logs</span>
</v-btn>
<v-btn text color="white" @click="() => (useStateStore().showLogModal = false)">
<v-icon left class="menu-icon"> mdi-close </v-icon>
<span class="menu-label">Close</span>
</v-btn>
</v-col>
</v-row>
<div class="pr-6 pl-6">
<v-btn-toggle v-model="selectedLogLevels" dark multiple class="fill mb-4 overflow-x-auto">
<v-btn v-for="level in [0, 1, 2, 3]" :key="level" color="secondary" class="fill">
{{ getLogLevelFromIndex(level) }}
</v-btn>
</v-btn-toggle>
<v-card-text v-if="logs.length === 0" style="font-size: 18px; font-weight: 600">
There are no logs to show
</v-card-text>
<v-virtual-scroll v-else :items="logs" item-height="50" height="600">
<template #default="{ item }">
<div :class="[getLogColor(item.level) + '--text', 'log-item']">
{{ item.message }}
</div>
</template>
</v-virtual-scroll>
</div>

<v-divider />

<v-card-actions>
<v-spacer />
<v-btn color="white" text @click="() => (useStateStore().showLogModal = false)"> Close </v-btn>
</v-card-actions>
<div class="dialog-data">
<!-- Log view options -->
<v-row class="pt-4 pt-md-0 no-gutters">
<v-col cols="12" md="5" class="align-self-center">
<v-text-field
v-model="searchQuery"
dark
dense
clearable
hide-details="auto"
prepend-icon="mdi-magnify"
color="accent"
label="Search"
/>
</v-col>
<v-col cols="12" md="2" style="display: flex; align-items: center">
<input v-model="timeInput" type="time" step="1" class="white--text pl-0 pl-md-8" />
<v-btn icon class="ml-3" @click="timeInput = undefined">
<v-icon>mdi-close-circle-outline</v-icon>
</v-btn>
</v-col>
<v-col cols="12" md="5" class="pr-3">
<v-row class="no-gutters">
<v-col v-for="level in [0, 1, 2, 3]" :key="level">
<v-row dense align="center">
<v-col cols="6" md="8" style="text-align: right">
{{ getLogLevelFromIndex(level) }}
</v-col>
<v-col cols="6" md="4">
<v-switch v-model="selectedLogLevels[level]" dark color="#ffd843" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whats the reason to replace the button toggle with switches?

</v-col>
</v-row>
</v-col>
</v-row>
</v-col>
</v-row>

<!-- Log entry list display -->
<div class="log-display">
<v-card-text v-if="!logs.length" style="font-size: 18px; font-weight: 150; height: 100%; text-align: center">
No available logs
</v-card-text>
<virtual-list
v-else
ref="logList"
style="height: 100%; overflow-y: auto"
data-key="index"
:data-sources="logs"
:data-component="LogEntry"
:estimate-size="35"
:keeps="logKeeps"
/>
</div>
</div>
</v-card>
</v-dialog>
</template>

<style scoped>
.v-btn-toggle.fill {
width: 100%;
height: 100%;
<style scoped lang="scss">
.dialog-container {
height: 90vh;
min-height: 300px !important;
}

.v-btn-toggle.fill > .v-btn {
width: 25%;
height: 100%;
.dialog-data {
/* Dialog size - dialog padding - header - divider */
height: calc(max(90vh, 300px) - 48px - 48px - 1px);
}
@media only screen and (max-width: 512px) {
.heading-container {
flex-direction: column;
padding-bottom: 14px;
}

.log-display {
/* Dialog data size - options */
height: calc(100% - 66px);
padding: 10px;
background-color: #232c37 !important;
border-radius: 5px;
}
@media only screen and (max-width: 312px) {
.open-icon {
margin: 0 !important;

@media only screen and (max-width: 960px) {
.log-display {
/* Dialog data size - options */
height: calc(100% - 118px);
}
.open-label {
}

@media only screen and (max-width: 700px) {
.menu-label {
display: none;
}

.menu-icon {
margin: 0 !important;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ const downloadCalibBoard = () => {
charucoImage.src = CharucoImage;
doc.addImage(charucoImage, "PNG", 0.25, 1.5, 8, 8);

doc.text(`8 x 8 | 1in & 0.75in`, paperWidth - 1, 1.0, {
doc.text("8 x 8 | 1in & 0.75in", paperWidth - 1, 1.0, {
maxWidth: (paperWidth - 2.0) / 2,
align: "right"
});
Expand Down Expand Up @@ -274,8 +274,8 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
:disabled="isCalibrating"
/>
<pv-select
v-model="tagFamily"
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="tagFamily"
label="Tag Family"
tooltip="Dictionary of aruco markers on the charuco board"
:select-cols="7"
Expand All @@ -291,8 +291,8 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
:label-cols="5"
/>
<pv-number-input
v-model="markerSizeIn"
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="markerSizeIn"
label="Marker Size (in)"
tooltip="Size of the tag markers in inches must be smaller than pattern spacing"
:disabled="isCalibrating"
Expand All @@ -316,8 +316,8 @@ const setSelectedVideoFormat = (format: VideoFormat) => {
:label-cols="5"
/>
<pv-switch
v-model="useOldPattern"
v-show="boardType == CalibrationBoardTypes.Charuco"
v-model="useOldPattern"
label="Old OpenCV Pattern"
:disabled="isCalibrating"
tooltip="If enabled, Photon will use the old OpenCV pattern for calibration."
Expand Down
4 changes: 2 additions & 2 deletions photon-client/src/components/settings/DeviceControlCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ const handleSettingsImport = () => {
<v-col cols="12" sm="6">
<v-btn color="secondary" @click="openExportLogsPrompt">
<v-icon left class="open-icon"> mdi-download </v-icon>
<span class="open-label">Download Current Log</span>
<span class="open-label">Download logs</span>

<!-- Special hidden link that gets 'clicked' when the user exports journalctl logs -->
<a
Expand All @@ -318,7 +318,7 @@ const handleSettingsImport = () => {
<v-col cols="12" sm="6">
<v-btn color="secondary" @click="useStateStore().showLogModal = true">
<v-icon left class="open-icon"> mdi-eye </v-icon>
<span class="open-label">Show log viewer</span>
<span class="open-label">View program logs</span>
</v-btn>
</v-col>
</v-row>
Expand Down
1 change: 1 addition & 0 deletions photon-client/src/types/SettingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export enum LogLevel {
export interface LogMessage {
level: LogLevel;
message: string;
timestamp: Date;
}

export interface Resolution {
Expand Down
Loading