Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/Frontend/src/components/CodeEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,19 @@ const extensions = computed(() => {

<style scoped>
.wrapper {
margin-top: 5px;
border-radius: 0.5rem;
padding: 0.5rem;
border: 1px solid #ccc;
background: white;
display: flex;
flex-direction: column;
}
.toolbar {
border-bottom: 1px solid #ccc;
padding-bottom: 0.5rem;
background-color: #f3f3f3;
border: #8c8c8c 1px solid;
border-radius: 3px;
padding: 5px;
margin-bottom: 0.5rem;
display: flex;
flex-direction: row;
Expand Down
18 changes: 12 additions & 6 deletions src/Frontend/src/components/messages2/BodyView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,17 @@ const body = computed(() => bodyState.value.data.value);
</script>

<template>
<div v-if="bodyState.not_found" class="alert alert-info">Could not find the message body. This could be because the message URL is invalid or the corresponding message was processed and is no longer tracked by ServiceControl.</div>
<div v-else-if="bodyState.failed_to_load" class="alert alert-info">Message body unavailable.</div>
<LoadingSpinner v-else-if="bodyState.loading" />
<CodeEditor v-else-if="body !== undefined && contentType.isSupported" :model-value="body" :language="contentType.language" :read-only="true" :show-gutter="true"></CodeEditor>
<div v-else class="alert alert-warning">Message body cannot be displayed because content type "{{ bodyState.data.content_type }}" is not supported.</div>
<div class="gap">
<div v-if="bodyState.not_found" class="alert alert-info">Could not find the message body. This could be because the message URL is invalid or the corresponding message was processed and is no longer tracked by ServiceControl.</div>
<div v-else-if="bodyState.failed_to_load" class="alert alert-info">Message body unavailable.</div>
<LoadingSpinner v-else-if="bodyState.loading" />
<CodeEditor v-else-if="body !== undefined && contentType.isSupported" :model-value="body" :language="contentType.language" :read-only="true" :show-gutter="true"></CodeEditor>
<div v-else class="alert alert-warning">Message body cannot be displayed because content type "{{ bodyState.data.content_type }}" is not supported.</div>
</div>
</template>

<style scoped></style>
<style scoped>
.gap {
margin-top: 5px;
}
</style>
79 changes: 44 additions & 35 deletions src/Frontend/src/components/messages2/FlowDiagram/FlowDiagram.vue
Original file line number Diff line number Diff line change
Expand Up @@ -218,44 +218,46 @@ const selectedErrorColor = hexToCSSFilter("#e8e6e8").filter;
</script>

<template>
<div v-if="store.conversationData.failed_to_load" class="alert alert-info">FlowDiagram data is unavailable.</div>
<LoadingSpinner v-else-if="store.conversationData.loading" />
<div v-else id="tree-container">
<VueFlow :nodes="nodes" :edges="edges" :min-zoom="0.1" :max-zoom="1.2" :only-render-visible-elements="true" @nodes-initialized="layoutGraph">
<Controls :show-interactive="false" position="top-left" class="controls" />
<template #node-message="{ id, data }: { id: string; data: NodeData }">
<TextEllipses class="address" :text="`${data.sendingEndpoint.name}@${data.sendingEndpoint.host}`" />
<div class="node" :class="{ error: data.isError, 'current-message': id === store.state.data.id }">
<div class="node-text">
<i
class="fa"
:style="data.isError && id === store.state.data.id ? { filter: selectedErrorColor } : {}"
:class="{ 'pa-flow-timeout': data.isTimeout, 'pa-flow-command': data.isCommand, 'pa-flow-event': data.isEvent }"
v-tippy="data.type"
/>
<div class="typeName">
<RouterLink v-if="data.isError" :to="{ path: routeLinks.messages.failedMessage.link(id), query: { back: backLink } }"><TextEllipses style="width: 204px" :text="data.label" ellipses-style="LeftSide" /></RouterLink>
<RouterLink v-else :to="{ path: routeLinks.messages.successMessage.link(data.messageId, id), query: { back: backLink } }"><TextEllipses style="width: 204px" :text="data.label" ellipses-style="LeftSide" /></RouterLink>
</div>
<i v-if="data.isError" class="fa pa-flow-failed" :style="id !== store.state.data.id ? { filter: errorColor } : { filter: selectedErrorColor }" />
<div class="time-sent">
<time-since class="time-since" :date-utc="data.timeSent" />
</div>
<div class="sagas" v-if="data.sagaInvocations.length > 0">
<div class="saga" v-for="saga in data.sagaInvocations" :key="saga.id">
<i
class="fa"
v-tippy="saga.isSagaInitiated ? 'Message originated from Saga' : !saga.isSagaInitiated && saga.isSagaCompleted ? 'Saga Completed' : 'Saga Initiated / Updated'"
:class="{ 'pa-flow-saga-initiated': saga.isSagaInitiated, 'pa-flow-saga-completed': !saga.isSagaInitiated && saga.isSagaCompleted, 'pa-flow-saga-trigger': !saga.isSagaInitiated && !saga.isSagaCompleted }"
/>
<div class="sagaName"><TextEllipses style="width: 182px" :text="saga.sagaType" ellipses-style="LeftSide" /></div>
<div class="gap">
<div v-if="store.conversationData.failed_to_load" class="alert alert-info">FlowDiagram data is unavailable.</div>
<LoadingSpinner v-else-if="store.conversationData.loading" />
<div v-else id="tree-container">
<VueFlow :nodes="nodes" :edges="edges" :min-zoom="0.1" :max-zoom="1.2" :only-render-visible-elements="true" @nodes-initialized="layoutGraph">
<Controls :show-interactive="false" position="top-left" class="controls" />
<template #node-message="{ id, data }: { id: string; data: NodeData }">
<TextEllipses class="address" :text="`${data.sendingEndpoint.name}@${data.sendingEndpoint.host}`" />
<div class="node" :class="{ error: data.isError, 'current-message': id === store.state.data.id }">
<div class="node-text">
<i
class="fa"
:style="data.isError && id === store.state.data.id ? { filter: selectedErrorColor } : {}"
:class="{ 'pa-flow-timeout': data.isTimeout, 'pa-flow-command': data.isCommand, 'pa-flow-event': data.isEvent }"
v-tippy="data.type"
/>
<div class="typeName">
<RouterLink v-if="data.isError" :to="{ path: routeLinks.messages.failedMessage.link(id), query: { back: backLink } }"><TextEllipses style="width: 204px" :text="data.label" ellipses-style="LeftSide" /></RouterLink>
<RouterLink v-else :to="{ path: routeLinks.messages.successMessage.link(data.messageId, id), query: { back: backLink } }"><TextEllipses style="width: 204px" :text="data.label" ellipses-style="LeftSide" /></RouterLink>
</div>
<i v-if="data.isError" class="fa pa-flow-failed" :style="id !== store.state.data.id ? { filter: errorColor } : { filter: selectedErrorColor }" />
<div class="time-sent">
<time-since class="time-since" :date-utc="data.timeSent" />
</div>
<div class="sagas" v-if="data.sagaInvocations.length > 0">
<div class="saga" v-for="saga in data.sagaInvocations" :key="saga.id">
<i
class="fa"
v-tippy="saga.isSagaInitiated ? 'Message originated from Saga' : !saga.isSagaInitiated && saga.isSagaCompleted ? 'Saga Completed' : 'Saga Initiated / Updated'"
:class="{ 'pa-flow-saga-initiated': saga.isSagaInitiated, 'pa-flow-saga-completed': !saga.isSagaInitiated && saga.isSagaCompleted, 'pa-flow-saga-trigger': !saga.isSagaInitiated && !saga.isSagaCompleted }"
/>
<div class="sagaName"><TextEllipses style="width: 182px" :text="saga.sagaType" ellipses-style="LeftSide" /></div>
</div>
</div>
</div>
</div>
</div>
<TextEllipses class="address" :text="`${data.receivingEndpoint.name}@${data.receivingEndpoint.host}`" />
</template>
</VueFlow>
<TextEllipses class="address" :text="`${data.receivingEndpoint.name}@${data.receivingEndpoint.host}`" />
</template>
</VueFlow>
</div>
</div>
</template>

Expand All @@ -268,6 +270,13 @@ const selectedErrorColor = hexToCSSFilter("#e8e6e8").filter;
<style scoped>
@import "../../list.css";

.gap {
margin-top: 5px;
border-radius: 0.5rem;
padding: 0.5rem;
border: 1px solid #ccc;
background: white;
}
.controls {
display: flex;
flex-wrap: wrap;
Expand Down
51 changes: 30 additions & 21 deletions src/Frontend/src/components/messages2/HeadersView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,44 @@ const filteredHeaders = computed(() => {
</script>

<template>
<div>
<div class="row filters">
<div class="col">
<div class="text-search-container">
<div class="text-search">
<div>
<FilterInput v-model="searchTerm" :aria-label="`Search for a header key or value`" :placeholder="'Search for a header key or value...'" />
<div class="gap">
<div>
<div class="row filters">
<div class="col">
<div class="text-search-container">
<div class="text-search">
<div>
<FilterInput v-model="searchTerm" :aria-label="`Search for a header key or value`" :placeholder="'Search for a header key or value...'" />
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="header-list" v-if="filteredHeaders.length > 0 && !headers.not_found">
<template v-for="(header, index) in filteredHeaders" :key="index">
<div class="header-key">{{ header.key }}</div>
<div class="header-value" @mouseover="toggleHover(index, true)" @mouseleave="toggleHover(index, false)">
<pre class="removeBootStrap">{{ header.value }}</pre>
<div class="clippy-button"><CopyToClipboard v-if="header.value && hoverStates[index]" :value="header.value" :isIconOnly="true" /></div>
</div>
</template>
</div>
<div class="header-list" v-if="filteredHeaders.length > 0 && !headers.not_found">
<template v-for="(header, index) in filteredHeaders" :key="index">
<div class="header-key">{{ header.key }}</div>
<div class="header-value" @mouseover="toggleHover(index, true)" @mouseleave="toggleHover(index, false)">
<pre class="removeBootStrap">{{ header.value }}</pre>
<div class="clippy-button"><CopyToClipboard v-if="header.value && hoverStates[index]" :value="header.value" :isIconOnly="true" /></div>
</div>
</template>
</div>

<!-- Message if filtered list is empty -->
<div v-if="filteredHeaders.length <= 0 && !headers.not_found" class="alert alert-warning">No headers found matching the search term.</div>
<div v-if="headers.not_found" class="alert alert-info">Could not find message headers. This could be because the message URL is invalid or the corresponding message was processed and is no longer tracked by ServiceControl.</div>
<!-- Message if filtered list is empty -->
<div v-if="filteredHeaders.length <= 0 && !headers.not_found" class="alert alert-warning">No headers found matching the search term.</div>
<div v-if="headers.not_found" class="alert alert-info">Could not find message headers. This could be because the message URL is invalid or the corresponding message was processed and is no longer tracked by ServiceControl.</div>
</div>
</template>

<style scoped>
.gap {
margin-top: 5px;
border-radius: 0.5rem;
padding: 0.5rem;
border: 1px solid #ccc;
background: white;
}
.removeBootStrap {
background: initial;
border: none;
Expand Down Expand Up @@ -78,7 +87,6 @@ const filteredHeaders = computed(() => {
}
.filters {
background-color: #f3f3f3;
margin-top: 5px;
border: #8c8c8c 1px solid;
border-radius: 3px;
padding: 5px;
Expand All @@ -92,6 +100,7 @@ const filteredHeaders = computed(() => {
align-items: flex-start;
justify-content: center;
row-gap: 2px;
background-color: #f3f3f3;
}

.header-value,
Expand Down
54 changes: 16 additions & 38 deletions src/Frontend/src/components/messages2/SagaDiagram.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import routeLinks from "@/router/routeLinks";
import { useSagaDiagramStore } from "@/stores/SagaDiagramStore";
import { useMessageStore } from "@/stores/MessageStore";
import { storeToRefs } from "pinia";
import ToolbarEndpointIcon from "@/assets/Shell_ToolbarEndpoint.svg";
import { SagaViewModel, parseSagaUpdates } from "./SagaDiagram/SagaDiagramParser";
import { typeToName } from "@/composables/typeHumanizer";
import LoadingSpinner from "@/components/LoadingSpinner.vue";
Expand Down Expand Up @@ -71,11 +70,8 @@ const vm = computed<SagaViewModel>(() => {
<template>
<div class="saga-container">
<!-- Toolbar header -->
<div v-if="vm.HasSagaData" class="header">
<button :class="['saga-button', { 'saga-button--active': vm.ShowMessageData }]" aria-label="show-message-data-button" @click="sagaDiagramStore.toggleMessageData">
<img class="saga-button-icon" :src="ToolbarEndpointIcon" alt="" />
{{ vm.ShowMessageData ? "Hide Message Data" : "Show Message Data" }}
</button>
<div v-if="vm.HasSagaData" class="toolbar">
<button type="button" class="btn btn-secondary btn-sm" aria-label="show-message-data-button" @click="sagaDiagramStore.toggleMessageData"><i class="fa fa-list-ul"></i> {{ vm.ShowMessageData ? "Hide Message Data" : "Show Message Data" }}</button>
</div>

<!-- Loading Spinner -->
Expand Down Expand Up @@ -111,16 +107,24 @@ const vm = computed<SagaViewModel>(() => {
.saga-container {
display: flex;
flex-direction: column;
/* Must validate parent height in order to set this element min-height value */
min-height: 500px;
background-color: #ffffff;
margin-top: 5px;
border-radius: 0.5rem;
padding: 0.5rem;
border: 1px solid #ccc;
background: white;
}

/* Main containers */

.header {
padding: 0.5rem;
border-bottom: solid 2px #ddd;
.toolbar {
background-color: #f3f3f3;
border: #8c8c8c 1px solid;
border-radius: 3px;
padding: 5px;
margin-bottom: 0.5rem;
display: flex;
flex-direction: row;
min-height: 40px;
}
.body {
display: flex;
Expand All @@ -140,30 +144,4 @@ const vm = computed<SagaViewModel>(() => {
align-items: center;
min-height: 200px;
}

/* Button styles */

.saga-button {
display: block;
padding: 0.2rem 0.7rem 0.1rem;
color: #555555;
font-size: 0.75rem;
border: solid 2px #00a3c4;
background-color: #e3e4e5;
}
.saga-button:focus,
.saga-button:hover {
background-color: #daebfc;
}

.saga-button:active,
.saga-button--active {
background-color: #c3dffc;
}
.saga-button-icon {
width: 0.75rem;
height: 0.75rem;
margin-top: -0.2rem;
margin-right: 0.25rem;
}
</style>
40 changes: 32 additions & 8 deletions src/Frontend/src/components/messages2/SequenceDiagram.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,42 @@ onMounted(() => store.refreshConversation());
</script>

<template>
<a class="help-link" target="_blank" href="https://docs.particular.net/servicepulse/sequence-diagram"><i class="fa fa-info-circle" /> Sequence Diagram Help</a>
<div class="outer" @scroll="(ev) => (endpointYOffset = (ev.target as Element).scrollTop)">
<svg class="sequence-diagram" :style="{ width: `max(100%, ${isNaN(maxWidth) ? 0 : maxWidth}px)` }" :height="maxHeight + 20">
<Timeline />
<Handlers />
<Routes />
<Endpoints :yOffset="endpointYOffset" />
</svg>
<div class="wrapper">
<div class="toolbar">
<a class="help-link" target="_blank" href="https://docs.particular.net/servicepulse/sequence-diagram"><i class="fa fa-info-circle" /> Sequence Diagram Help</a>
</div>
<div class="outer" @scroll="(ev) => (endpointYOffset = (ev.target as Element).scrollTop)">
<svg class="sequence-diagram" :style="{ width: `max(100%, ${isNaN(maxWidth) ? 0 : maxWidth}px)` }" :height="maxHeight + 20">
<Timeline />
<Handlers />
<Routes />
<Endpoints :yOffset="endpointYOffset" />
</svg>
</div>
</div>
</template>

<style scoped>
.wrapper {
margin-top: 5px;
border-radius: 0.5rem;
padding: 0.5rem;
border: 1px solid #ccc;
background: white;
display: flex;
flex-direction: column;
}
.toolbar {
background-color: #f3f3f3;
border: #8c8c8c 1px solid;
border-radius: 3px;
padding: 5px;
margin-bottom: 0.5rem;
display: flex;
flex-direction: row;
justify-content: end;
min-height: 40px;
}
.outer {
max-width: 100%;
max-height: calc(100vh - 27em);
Expand Down