fixing error in session refresh, fixing error in common.js/buildDate, added enhanced information to error log detail page

This commit is contained in:
Jan 2025-11-10 17:14:49 +01:00
parent 5d775a7dda
commit 2975c8c3a1
8 changed files with 466 additions and 28 deletions

View file

@ -33,7 +33,7 @@ const trackActivity = () => {
}
export const startSessionRefresh = () => {
const startSessionRefresh = () => {
['mousedown', 'keypress', 'scroll', 'touchstart'].forEach(event => {
document.addEventListener(event, trackActivity);
@ -53,7 +53,7 @@ export const startSessionRefresh = () => {
}, 10 * 60 * 1000);
}
export const stopSessionRefresh = () => {
const stopSessionRefresh = () => {
if (sessionRefreshInterval) {
clearInterval(sessionRefreshInterval);
}

View file

@ -161,17 +161,20 @@ export class UrlSafeBase64 {
}
}
export const buildDate = (date, time) => {
if (!date) return 'unkown';
export const buildDate = (dateStr, time) => {
if (!dateStr) return 'unknown';
const date = new Date(dateStr);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
if (!time) return `${year}/${month}/${day}`;
const hours = time.getHours().toString().padStart(2, '0');
const minutes = time.getMinutes().toString().padStart(2, '0');
const seconds = time.getSeconds().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
}

View file

@ -0,0 +1,91 @@
<template>
<div class="error-modal-container">
<div class="trace-view-header">
<h3 class="sub-header"> Error Details <br><span class="trace-view-message">{{ message }}</span>
</h3>
<icon-button icon="x" @click="$emit('close')"></icon-button>
</div>
<div class="trace-view-body">
<tab-container :tabs="tabsError" class="tab-container" @tab-changed="handleTabChange">
</tab-container>
</div>
</div>
</template>
<script>
import TabContainer from "@/components/UI/TabContainer.vue";
import {markRaw} from "vue";
import ErrorModalTraceView from "@/components/layout/error/ErrorModalTraceView.vue";
import IconButton from "@/components/UI/IconButton.vue";
import ErrorModalOverview from "@/components/layout/error/ErrorModalOverview.vue";
import ErrorModalPiniaStore from "@/components/layout/error/ErrorModalPiniaStore.vue";
export default {
name: "ErrorModal",
components: {IconButton, TabContainer},
emits: ["close"],
props: {
error: {
type: Object,
required: true
}
},
computed: {
tabsError() {
return [
{
title: 'Overview',
component: markRaw(ErrorModalOverview),
props: {isSelected: false, error: this.error},
},
{
title: 'Stack trace',
component: markRaw(ErrorModalTraceView),
props: {isSelected: false, error: this.error},
},
{
title: 'Pinia store',
component: markRaw(ErrorModalPiniaStore),
props: {isSelected: false, error: this.error},
},
]
}
},
}
</script>
<style scoped>
.error-modal-container {
height: 90vh;
width: 80vw;
display: flex;
flex-direction: column;
overflow: hidden; /* Verhindert Overflow */
}
.trace-view-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0; /* Header soll nicht schrumpfen */
padding-bottom: 1.6rem; /* Optional: etwas Abstand */
}
.trace-view-body {
flex: 1; /* Nimmt den restlichen Platz ein */
min-height: 0; /* Wichtig für Flexbox-Scrolling */
overflow: hidden; /* Container selbst soll nicht scrollen */
}
.tab-container {
height: 100%; /* Füllt die trace-view-body aus */
}
</style>

View file

@ -0,0 +1,252 @@
<template>
<div class="error-overview-container">
<div class="error-overview">
<div class="error-section">
<div class="error-label">Type</div>
<div class="error-value">
<basic-badge :variant="badgeVariant">{{ error.type || 'UNKNOWN' }}</basic-badge>
</div>
</div>
<div class="error-section">
<div class="error-label">Title</div>
<div class="error-value">
{{ error.title || 'No title provided' }}
</div>
</div>
<div class="error-section">
<div class="error-label">Details</div>
<div class="error-value">
{{ error.message || 'No details available' }}
</div>
</div>
<div class="error-section">
<div class="error-label">Exception</div>
<div class="error-value">
{{ error.code || 'N/A' }}
</div>
</div>
<div class="error-section">
<div class="error-label">Timestamp</div>
<div class="error-value">
{{ formattedTimestamp }}
</div>
</div>
<div class="error-section">
<div class="error-label">User ID</div>
<div class="error-value">
{{ error.user_id || 'N/A' }}
</div>
</div>
<div class="error-section" v-if="errorLocation">
<div class="error-label">File</div>
<div class="error-value">
{{ errorLocation }}
</div>
</div>
<div class="error-section" v-if="errorMethod">
<div class="error-label">Method</div>
<div class="error-value">
{{ errorMethod }}
</div>
</div>
<div class="error-section" v-if="error.calculation_job_id">
<div class="error-label">Calculation Job ID</div>
<div class="error-value">
{{ error.calculation_job_id }}
</div>
</div>
<div class="error-section" v-if="error.bulk_operation_id">
<div class="error-label">Bulk Operation ID</div>
<div class="error-value">
{{ error.bulk_operation_id }}
</div>
</div>
</div>
</div>
</template>
<script>
import BasicBadge from "@/components/UI/BasicBadge.vue";
import {buildDate} from "@/common.js";
export default {
name: "ErrorModalOverview",
components: {BasicBadge},
props: {
isSelected: {
type: Boolean,
required: true,
},
error: {
type: Object,
required: true,
}
},
computed: {
badgeVariant() {
switch (this.error.type) {
case "FRONTEND":
return "primary";
case "BACKEND":
return "secondary";
default:
return "grey";
}
},
formattedTimestamp() {
if (!this.error.timestamp) return 'N/A';
try {
return buildDate(this.error.timestamp, true);
} catch (e) {
return this.error.timestamp;
}
},
errorLocation() {
if (!this.error.trace || this.error.trace.length === 0) {
return null;
}
const firstTrace = this.error.trace[0];
return `${firstTrace.file}:${firstTrace.line}`;
},
errorMethod() {
if (!this.error.trace || this.error.trace.length === 0) {
return null;
}
const firstTrace = this.error.trace[0];
return `${firstTrace.method}`;
}
}
}
</script>
<style scoped>
code {
font-family: Roboto Mono, monospace;
font-size: 1.2rem;
}
.error-overview-container {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
overflow: hidden;
}
.error-overview {
overflow: auto;
padding: 1.6rem;
display: flex;
flex-direction: column;
gap: 1.6rem;
flex: 1;
min-height: 0;
}
.error-section {
display: grid;
grid-template-columns: 20rem 1fr;
align-items: center;
padding: 1.6rem;
gap: 0.6rem;
border-bottom: 0.16rem solid #f3f4f6;
}
.error-label {
font-weight: 500;
font-size: 1.4rem;
color: #6B869C;
text-transform: uppercase;
letter-spacing: 0.05rem;
}
.error-value {
color: #6b7280;
font-size: 1.4rem;
word-break: break-word;
}
.error-title {
font-weight: 500;
color: #d32f2f;
}
.error-message {
color: #666;
}
.error-code {
font-family: 'Roboto Mono', monospace;
background-color: #f5f5f5;
padding: 0.8rem 1.2rem;
border-radius: 0.4rem;
border: 1px solid #e0e0e0;
}
.error-code code {
color: #c7254e;
font-size: 1.3rem;
}
.error-location {
font-family: 'Roboto Mono', monospace;
background-color: #f5f5f5;
padding: 0.8rem 1.2rem;
border-radius: 0.4rem;
border: 1px solid #e0e0e0;
}
.error-location code {
color: #1976d2;
font-size: 1.2rem;
}
.type-badge {
display: inline-block;
padding: 0.6rem 1.2rem;
border-radius: 0.4rem;
font-weight: 600;
font-size: 1.3rem;
width: fit-content;
text-transform: uppercase;
letter-spacing: 0.05rem;
}
.type-backend {
background-color: #ffebee;
color: #c62828;
border: 1px solid #ef5350;
}
.type-frontend {
background-color: #fff3e0;
color: #e65100;
border: 1px solid #ff9800;
}
.type-unknown {
background-color: #f5f5f5;
color: #757575;
border: 1px solid #bdbdbd;
}
</style>

View file

@ -0,0 +1,90 @@
<template>
<div v-if="piniaData" class="pinia-store-viewer">
<div class="store-content">
<pre class="json-display">{{ formattedPiniaData }}</pre>
</div>
</div>
<div v-else class="no-data">
<p>No pinia data</p>
</div>
</template>
<script>
import logger from "@/logger.js";
export default {
name: "ErrorModalPiniaStore",
props: {
error: {
type: Object,
required: true,
}
},
computed: {
piniaData() {
if (!this.error?.pinia) return null;
if (typeof this.error.pinia === 'object') {
return this.error.pinia;
}
if (typeof this.error.pinia === 'string') {
try {
return JSON.parse(this.error.pinia);
} catch (e) {
logger.error('Error parsing error.pinia:', e);
return { raw: this.error.pinia };
}
}
return null;
},
formattedPiniaData() {
if (!this.piniaData) return '';
return JSON.stringify(this.piniaData, null, 2);
}
}
}
</script>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Cal+Sans&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap');
.pinia-store-viewer {
margin-top: 1rem;
height: 100%;
overflow-y: auto;
}
.section-title {
font-size: 1rem;
font-weight: 600;
margin-bottom: 0.75rem;
color: #374151;
}
.store-content {
height: 100%
}
.json-display {
margin: 0;
font-family: 'Roboto mono', monospace;
font-size: 1.2rem;
flex: 1;
padding: 0 1.6rem;
color: #002F54;
white-space: pre-wrap;
word-break: break-word;
height: 100%
}
.no-data {
padding: 1rem;
text-align: center;
color: #6b7280;
font-style: italic;
}
</style>

View file

@ -2,9 +2,8 @@
<div class="trace-view-container" :class="trace ? '' : 'trace-view-container--no-trace'">
<div class="trace-view-header">
<h3 class="sub-header">{{ title }} <br><span class="trace-view-message">{{ message }}</span>
</h3>
<icon-button icon="x" @click="$emit('close')"></icon-button>
<div class="trace-view-exception">{{ title }} - {{ message }}</div>
</div>
<div v-if="trace" class="trace-view">
<div class="trace-view-exception">{{ exception }}</div>
@ -73,7 +72,6 @@ export default {
@import url('https://fonts.googleapis.com/css2?family=Cal+Sans&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Roboto+Mono:ital,wght@0,100..700;1,100..700&display=swap');
.highlight-trace-item {
color: #002F54;
background-color: #F2F2F2;
padding-left: 1.6rem;
@ -89,12 +87,16 @@ export default {
font-size: 1.2rem;
flex: 1;
padding: 0 1.6rem;
min-height: 0; /* Wichtig für Flexbox-Scrolling */
}
.trace-view-header {
font-family: Roboto Mono, monospace;
font-size: 1.2rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0; /* Header soll nicht schrumpfen */
}
.trace-view-message {
@ -107,16 +109,16 @@ export default {
}
.trace-view-exception {
/* Dein Styling hier */
}
.trace-view-container {
height: 90vh;
width: 80vw;
display: flex;
flex-direction: column;
gap: 1.6rem;
height: 100%; /* Parent-Höhe ausfüllen */
width: 100%; /* Parent-Breite ausfüllen */
overflow: hidden; /* Container selbst soll nicht scrollen */
}
.trace-view-container--no-trace {

View file

@ -1,7 +1,7 @@
<template>
<div>
<modal :state="showModal">
<error-log-modal :error="error" @close="showModal = false"></error-log-modal>
<error-modal :error="error" @close="showModal = false"></error-modal>
</modal>
<table-view :columns="columns" :data-source="fetchData" :page="pagination.page" :page-size="pageSize" :page-count="pagination.pageCount" :total-count="pagination.totalCount" @row-click="showDetails" :mouse-over="true"></table-view>
@ -16,18 +16,18 @@ import {useErrorLogStore} from "@/store/errorLog.js";
import {mapStores} from "pinia";
import Modal from "@/components/UI/Modal.vue";
import TraceView from "@/components/layout/TraceView.vue";
import ErrorLogModal from "@/components/layout/ErrorLogModal.vue";
import {buildDate} from "@/common.js";
import ErrorModal from "@/components/layout/error/ErrorModal.vue";
export default {
name: "ErrorLog",
components: {ErrorLogModal, TraceView, Modal, TableView},
components: {ErrorModal, TraceView, Modal, TableView},
data() {
return {
showModal: false,
error: null,
pageSize: 20,
pagination: { page: 1, pageCount: 10, totalCount: 1 },
pagination: { page: 1, pageCount: 1, totalCount: 1 },
columns: [
{key: 'type', label: 'Type', align: "center", iconResolver: (rawValue, item) => {

View file

@ -30,13 +30,13 @@ public class HandlingCostCalculationService {
this.shippingFrequencyCalculationService = shippingFrequencyCalculationService;
}
public HandlingResult doCalculation(Integer setId, Premise premise, Destination destination, Boolean addRepackingCosts) {
public HandlingResult doCalculation(Integer setId, Premise premise, Destination destination, Boolean addRepackingAndDisposalCost) {
var hu = premiseToHuService.createHuFromPremise(premise);
return (LoadCarrierType.SLC == hu.getLoadCarrierType() ? getSLCCost(setId, destination, hu, hu.getLoadCarrierType(), addRepackingCosts) : getLLCCost(setId, destination, hu, hu.getLoadCarrierType(), addRepackingCosts));
return (LoadCarrierType.SLC == hu.getLoadCarrierType() ? getSLCCost(setId, destination, hu, hu.getLoadCarrierType(), addRepackingAndDisposalCost) : getLLCCost(setId, destination, hu, hu.getLoadCarrierType(), addRepackingAndDisposalCost));
}
private HandlingResult getSLCCost(Integer setId, Destination destination, PackagingDimension hu, LoadCarrierType loadCarrierType, boolean addRepackingCosts) {
private HandlingResult getSLCCost(Integer setId, Destination destination, PackagingDimension hu, LoadCarrierType loadCarrierType, boolean addRepackingAndDisposalCost) {
var destinationHandling = destination.getHandlingCost();
var destinationDisposal = destination.getDisposalCost();
@ -46,16 +46,16 @@ public class HandlingCostCalculationService {
BigDecimal handling = destinationHandling != null ? destinationHandling : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_HANDLING, setId).orElseThrow().getCurrentValue()));
BigDecimal release = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_RELEASE, setId).orElseThrow().getCurrentValue()));
BigDecimal dispatch = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_DISPATCH, setId).orElseThrow().getCurrentValue()));
BigDecimal disposal = destinationDisposal != null ? destinationDisposal : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL, setId).orElseThrow().getCurrentValue()));
BigDecimal disposal = destinationDisposal != null ? destinationDisposal : (addRepackingAndDisposalCost ? BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL, setId).orElseThrow().getCurrentValue())) : BigDecimal.ZERO);
BigDecimal wageFactor = BigDecimal.valueOf(Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, setId, destination.getCountryId()).orElseThrow().getCurrentValue()));
BigDecimal booking = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING_KLT, setId).orElseThrow().getCurrentValue()));
return new HandlingResult(LoadCarrierType.SLC,
getRepackingCost(setId, hu, loadCarrierType, addRepackingCosts, destinationRepacking).multiply(huAnnualAmount),
getRepackingCost(setId, hu, loadCarrierType, addRepackingAndDisposalCost, destinationRepacking).multiply(huAnnualAmount),
handling.multiply(huAnnualAmount),
destinationDisposal == null ? BigDecimal.ZERO : (disposal.multiply(huAnnualAmount)), //TODO: disposal SLC, ignore?
huAnnualAmount.multiply((handling.add(booking).add(release).add(dispatch).add(getRepackingCost(setId, hu, loadCarrierType, addRepackingCosts, destinationRepacking)))).multiply(wageFactor));
huAnnualAmount.multiply((handling.add(booking).add(release).add(dispatch).add(getRepackingCost(setId, hu, loadCarrierType, addRepackingAndDisposalCost, destinationRepacking)))).multiply(wageFactor));
}
@ -77,7 +77,7 @@ public class HandlingCostCalculationService {
}
private HandlingResult getLLCCost(Integer setId, Destination destination, PackagingDimension hu, LoadCarrierType type, boolean addRepackingCosts) {
private HandlingResult getLLCCost(Integer setId, Destination destination, PackagingDimension hu, LoadCarrierType type, boolean addRepackingAndDisposalCost) {
var destinationHandling = destination.getHandlingCost();
var destinationDisposal = destination.getDisposalCost();
@ -87,12 +87,12 @@ public class HandlingCostCalculationService {
BigDecimal handling = destinationHandling != null ? destinationHandling : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_HANDLING, setId).orElseThrow().getCurrentValue()));
BigDecimal release = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_RELEASE, setId).orElseThrow().getCurrentValue()));
BigDecimal dispatch = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.GLT_DISPATCH, setId).orElseThrow().getCurrentValue()));
BigDecimal disposal = destinationDisposal != null ? destinationDisposal : BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL, setId).orElseThrow().getCurrentValue()));
BigDecimal disposal = destinationDisposal != null ? destinationDisposal : (addRepackingAndDisposalCost ? BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.DISPOSAL, setId).orElseThrow().getCurrentValue())) : BigDecimal.ZERO);
BigDecimal wageFactor = BigDecimal.valueOf(Double.parseDouble(countryPropertyRepository.getByMappingIdAndCountryId(CountryPropertyMappingId.WAGE, setId, destination.getCountryId()).orElseThrow().getCurrentValue()));
BigDecimal booking = BigDecimal.valueOf(Double.parseDouble(propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.BOOKING, setId).orElseThrow().getCurrentValue()));
var annualRepacking = getRepackingCost(setId, hu, type, addRepackingCosts, destinationRepacking).multiply(wageFactor).multiply( huAnnualAmount);
var annualRepacking = getRepackingCost(setId, hu, type, addRepackingAndDisposalCost, destinationRepacking).multiply(wageFactor).multiply( huAnnualAmount);
var annualHandling = ((handling.add(dispatch).add(release)).multiply(wageFactor).multiply(huAnnualAmount)).add(booking.multiply(BigDecimal.valueOf(shippingFrequencyCalculationService.doCalculation(setId, huAnnualAmount.doubleValue()))));
var annualDisposal = (disposal.multiply(huAnnualAmount));