Skip to content
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
40bff1b
Bump Particular.Packaging from 4.1.0 to 4.2.0 in /src
dependabot[bot] Nov 22, 2024
03152f8
Bump Microsoft.NET.Test.Sdk from 17.11.1 to 17.12.0 in /src
dependabot[bot] Nov 22, 2024
acf512f
Bump actions/upload-artifact from 4.4.3 to 4.5.0
dependabot[bot] Dec 18, 2024
98fc4b3
Bump actions/setup-dotnet from 4.1.0 to 4.2.0
dependabot[bot] Dec 27, 2024
6eb6bdb
Bump actions/upload-artifact from 4.5.0 to 4.6.0
dependabot[bot] Jan 9, 2025
6b74f6a
Merge remote-tracking branch 'public_origin/master'
johnsimons Jan 16, 2025
8f2bff1
Disable sync and dependabot
johnsimons Jan 16, 2025
77941f8
conditionally show messages according to configuration definition of …
PhilBastian Nov 4, 2024
b3db99d
escape forward slashes in endpoint names
PhilBastian Nov 6, 2024
d151611
support filtering out endpoints from non-heartbeat supporting connect…
PhilBastian Nov 21, 2024
28df921
reflect new configuration shape
PhilBastian Nov 22, 2024
d6da487
don't show unmonitored endpoints on the monitoring screen, even if th…
PhilBastian Nov 22, 2024
0b0d2ec
handle intent header not existing
PhilBastian Nov 22, 2024
f5684f0
show masstransit message if pending retries is enabled
PhilBastian Nov 22, 2024
9f32227
include new api property in tests
PhilBastian Nov 25, 2024
e65010a
restore awaiting config before loading message
PhilBastian Nov 25, 2024
340d559
remove text "Note:" since it's styled as a note
PhilBastian Nov 29, 2024
f519cb3
A small fix
johnsimons Jan 16, 2025
0f2e91c
Adding masstransit connector tab
johnsimons Jan 20, 2025
960a4d8
Hide "Open in ServiceInsight" and "Flow diagram" in ServicePulse
johnsimons Jan 20, 2025
f9c08e8
Remove supports_heartbeats
johnsimons Jan 20, 2025
dbc9e42
Add link to learn about connector
johnsimons Jan 23, 2025
7432751
style using existing colours and formats, and change pixel sizing to em
PhilBastian Jan 23, 2025
64bca49
handle long queue names
PhilBastian Jan 23, 2025
7bc8871
sizing and format of queue names and logs
PhilBastian Jan 23, 2025
9ca8157
replace non-production references for masstransit with early access
PhilBastian Jan 23, 2025
abd14ed
allow for no monitoring url in settings
PhilBastian Jan 23, 2025
4d6d608
Merge branch 'master' into mass_transit_customisation
PhilBastian Jan 23, 2025
cf44963
Revert "Disable sync and dependabot"
johnsimons Jan 23, 2025
8c8e63e
A few small changes based on code review
johnsimons Jan 24, 2025
3258c7d
Don't add proxy config for monitoring if monitoring is disabled
johnsimons Jan 24, 2025
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
7 changes: 6 additions & 1 deletion src/Frontend/src/components/LicenseNotifications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import { useShowToast } from "@/composables/toast";
import { TYPE } from "vue-toastification";
import routeLinks from "@/router/routeLinks";
import { useRouter } from "vue-router";
import { useConfiguration } from "@/composables/configuration";

const router = useRouter();
const { license, getOrUpdateLicenseStatus } = useLicense();

const configuration = useConfiguration();

function displayWarningMessage(licenseStatus: LicenseStatus) {
const configurationRootLink = router.resolve(routeLinks.configuration.root).href;
switch (licenseStatus) {
Expand All @@ -19,7 +22,9 @@ function displayWarningMessage(licenseStatus: LicenseStatus) {
break;
}
case "ValidWithExpiringTrial": {
const trialExpiring = `<div><strong>Non-production development license expiring</strong><div>Your non-production development license will expire soon. To continue using the Particular Service Platform you'll need to extend your license.</div><a href="${license.license_extension_url}" class="btn btn-warning"><i class="fa fa-external-link-alt"></i> Extend your license</a><a href="${configurationRootLink}" class="btn btn-light">View license details</a></div>`;
const trialExpiring = configuration.value?.mass_transit_connector
? `<div><strong>Early Access license expiring</strong><div>Your Early Access license will expire soon. To continue using the Particular Service Platform you'll need to extend your license.</div><a href="${license.license_extension_url}" class="btn btn-warning"><i class="fa fa-external-link-alt"></i> Extend your license</a><a href="${configurationRootLink}" class="btn btn-light">View license details</a></div>`
: `<div><strong>Non-production development license expiring</strong><div>Your non-production development license will expire soon. To continue using the Particular Service Platform you'll need to extend your license.</div><a href="${license.license_extension_url}" class="btn btn-warning"><i class="fa fa-external-link-alt"></i> Extend your license</a><a href="${configurationRootLink}" class="btn btn-light">View license details</a></div>`;
useShowToast(TYPE.WARNING, "", trialExpiring, true);
break;
}
Expand Down
5 changes: 4 additions & 1 deletion src/Frontend/src/components/PageFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { monitoringUrl, serviceControlUrl } from "../composables/serviceServiceC
import { license, licenseStatus } from "../composables/serviceLicense";
import { LicenseStatus } from "@/resources/LicenseInfo";
import routeLinks from "@/router/routeLinks";
import { useConfiguration } from "@/composables/configuration";
const isMonitoringEnabled = computed(() => {
return monitoringUrl.value !== "!" && monitoringUrl.value !== "" && monitoringUrl.value !== null && monitoringUrl.value !== undefined;
Expand All @@ -17,6 +18,8 @@ const scAddressTooltip = computed(() => {
const scMonitoringAddressTooltip = computed(() => {
return `Monitoring URL ${monitoringUrl.value}`;
});
const configuration = useConfiguration();
</script>

<template>
Expand Down Expand Up @@ -64,7 +67,7 @@ const scMonitoringAddressTooltip = computed(() => {
</template>
</div>
</div>
<template v-if="license.license_status !== LicenseStatus.Unavailable && licenseStatus.isTrialLicense">
<template v-if="license.license_status !== LicenseStatus.Unavailable && !configuration?.mass_transit_connector && licenseStatus.isTrialLicense">
<div class="row trialLicenseBar">
<div role="status" aria-label="trial license bar information">
<RouterLink :to="routeLinks.configuration.license.link">{{ license.license_type }} license</RouterLink>, non-production use only
Expand Down
99 changes: 99 additions & 0 deletions src/Frontend/src/components/configuration/MassTransitConnector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<script setup lang="ts">
import { useConfiguration } from "@/composables/configuration";
import moment from "moment";

const configuration = useConfiguration();
// "Wed, Jan 15th 2025 10:56:21 +10:00",
function formatDate(date: string) {
return moment(date).local().format("LLLL"); //.format("ddd, MMM Do YYYY HH:mm:ss Z");
}
</script>

<template>
<div class="box" v-if="configuration?.mass_transit_connector !== undefined">
<div class="row margin-bottom-10">
<h4>
Connector Version: <span class="version-format">{{ configuration.mass_transit_connector.version }}</span>
</h4>
</div>
<div class="row margin-bottom-10">
<h4>List of error queues configured in the connector.</h4>
<div class="queues-container">
<div class="row margin-gap hover-highlight" v-for="queue in configuration.mass_transit_connector.error_queues" :key="queue.name">
<div :title="queue.name">{{ queue.name }}</div>
<div class="error-color" v-if="!queue.ingesting">Not ingesting</div>
<div class="ok-color" v-else>Ok</div>
</div>
</div>
</div>
<div class="row">
<h4>The entries below are the most recent warning and error-level events recorded on the ServiceControl Connector.</h4>
<div class="logs-container">
<div class="row margin-gap hover-highlight" v-for="log in [...configuration.mass_transit_connector.logs].reverse()" :key="log.date">
<div class="col-2">{{ formatDate(log.date) }}</div>
<div class="col-1" :class="`${log.level.toLowerCase()}-color`">{{ log.level }}</div>
<div class="col-9" :class="`${log.level.toLowerCase()}-color`">
<pre>{{ log.message }}</pre>
</div>
</div>
</div>
</div>
</div>
<div class="box" v-else>
<p>MassTransit Connector for ServiceControl is not configured.</p>
<p><a target="_blank" href="https://particular.net/learn-more-about-masstransit-connector">Learn more about the MassTransit Connector.</a></p>
</div>
</template>

<style scoped>
.hover-highlight:hover {
background-color: #ededed;
}

.margin-gap {
margin-bottom: 3px;
}

.queues-container {
max-width: 100%;
width: fit-content;
padding: 0.75rem;
}
.queues-container .row {
display: grid;
grid-template-columns: 5fr minmax(10em, 1fr);
}
.queues-container .row div {
overflow-wrap: anywhere;
}

.logs-container {
padding: 0.75rem;
}
.version-format {
font-weight: bold;
}
.box > .row:not(:last-child) {
padding-bottom: 0.5rem;
border-bottom: 1px solid #ccc;
margin-bottom: 0.5rem;
}

.logs-container pre {
all: revert;
margin: 0;
font-size: 0.9rem;
overflow-wrap: break-word;
text-wrap: auto;
}

.warning-color {
color: var(--bs-warning);
}
.error-color {
color: var(--bs-danger);
}
.ok-color {
color: var(--bs-success);
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { connectionState } from "@/composables/serviceServiceControl";
import BusyIndicator from "../BusyIndicator.vue";
import ExclamationMark from "./../../components/ExclamationMark.vue";
import convertToWarningLevel from "@/components/configuration/convertToWarningLevel";
import { useConfiguration } from "@/composables/configuration";
import { typeText } from "@/resources/LicenseInfo";
// This is needed because the ConfigurationView.vue routerView expects this event.
// The event is only actually emitted on the RetryRedirects.vue component
Expand All @@ -18,6 +20,8 @@ defineEmits<{
const loading = computed(() => {
return !license;
});
const configuration = useConfiguration();
</script>

<template>
Expand All @@ -31,7 +35,7 @@ const loading = computed(() => {
<div class="box">
<div class="row">
<div class="license-info">
<div><b>Platform license type:</b> {{ license.license_type }}{{ license.licenseEdition }}</div>
<div><b>Platform license type:</b> {{ typeText(license, configuration) }}{{ license.licenseEdition }}</div>

<template v-if="licenseStatus.isSubscriptionLicense">
<div>
Expand Down
15 changes: 3 additions & 12 deletions src/Frontend/src/components/failedmessages/DeletedMessages.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { onBeforeMount, onMounted, onUnmounted, ref, watch } from "vue";
import { onMounted, onUnmounted, ref, watch } from "vue";
import { licenseStatus } from "../../composables/serviceLicense";
import { connectionState } from "../../composables/serviceServiceControl";
import { usePatchToServiceControl, useTypedFetchFromServiceControl } from "../../composables/serviceServiceControlUrls";
Expand All @@ -13,14 +13,13 @@ import ConfirmDialog from "../ConfirmDialog.vue";
import PaginationStrip from "../../components/PaginationStrip.vue";
import moment from "moment";
import { ExtendedFailedMessage } from "@/resources/FailedMessage";
import Configuration from "@/resources/Configuration";
import { TYPE } from "vue-toastification";
import FailureGroup from "@/resources/FailureGroup";
import { useConfiguration } from "@/composables/configuration";

let pollingFaster = false;
let refreshInterval: number | undefined;
const perPage = 50;
const configuration = ref<Configuration | null>(null);

const route = useRoute();
const groupId = ref<string>(route.params.groupId as string);
Expand All @@ -36,6 +35,7 @@ const messageList = ref<IMessageList | undefined>();
const messages = ref<ExtendedFailedMessage[]>([]);

watch(pageNumber, () => loadMessages());
const configuration = useConfiguration();

function loadMessages() {
let startDate = new Date(0);
Expand Down Expand Up @@ -162,11 +162,6 @@ function periodChanged(period: PeriodOption) {
loadMessages();
}

async function getConfiguration() {
const [, data] = await useTypedFetchFromServiceControl<Configuration>("configuration");
configuration.value = data;
}

function isRestoreInProgress() {
return messages.value.some((message) => message.restoreInProgress);
}
Expand Down Expand Up @@ -196,10 +191,6 @@ onBeforeRouteLeave(() => {
groupName.value = "";
});

onBeforeMount(() => {
getConfiguration();
});

onUnmounted(() => {
if (refreshInterval != null) {
window.clearInterval(refreshInterval);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ function isContentTypeSupported(contentType: string) {

function getMessageIntent() {
const intent = findHeadersByKey("NServiceBus.MessageIntent");
return intent.value;
return intent?.value ?? "";
}

function removeHeadersMarkedAsRemoved() {
Expand Down
36 changes: 23 additions & 13 deletions src/Frontend/src/components/failedmessages/MessageView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,20 @@ import ConfirmDialog from "../ConfirmDialog.vue";
import FlowDiagram from "./FlowDiagram.vue";
import EditRetryDialog from "./EditRetryDialog.vue";
import routeLinks from "@/router/routeLinks";
import Configuration, { EditAndRetryConfig } from "@/resources/Configuration";
import { EditAndRetryConfig } from "@/resources/Configuration";
import { TYPE } from "vue-toastification";
import { ExtendedFailedMessage, FailedMessageError, FailedMessageStatus, isError } from "@/resources/FailedMessage";
import Message from "@/resources/Message";
import { NServiceBusHeaders } from "@/resources/Header";
import { useConfiguration } from "@/composables/configuration";
import { useIsMassTransitConnected } from "@/composables/connectedApplications";
import { parse, stringify } from "lossless-json";

let refreshInterval: number | undefined;
let pollingFaster = false;
const panel = ref<number>(1);
const route = useRoute();
const failedMessage = ref<ExtendedFailedMessage | FailedMessageError>();
const configuration = ref<Configuration>();
const editAndRetryConfiguration = ref<EditAndRetryConfig>();

const id = computed(() => route.params.id as string);
Expand All @@ -35,6 +36,9 @@ const showRestoreConfirm = ref(false);
const showRetryConfirm = ref(false);
const showEditRetryModal = ref(false);

const configuration = useConfiguration();
const isMassTransitConnected = useIsMassTransitConnected();

async function loadFailedMessage() {
try {
const response = await useFetchFromServiceControl("errors/last/" + id.value);
Expand Down Expand Up @@ -71,12 +75,6 @@ async function loadFailedMessage() {
}
}

async function getConfiguration() {
const response = await useFetchFromServiceControl("configuration");
configuration.value = await response.json();
return getEditAndRetryConfig();
}

async function getEditAndRetryConfig() {
const [, data] = await useTypedFetchFromServiceControl<EditAndRetryConfig>("edit/config");

Expand Down Expand Up @@ -365,7 +363,7 @@ function changeRefreshInterval(milliseconds: number) {
onMounted(async () => {
togglePanel(1);

await getConfiguration();
await getEditAndRetryConfig();
startRefreshInterval();
loadFailedMessage();
});
Expand Down Expand Up @@ -407,8 +405,8 @@ onUnmounted(() => {
<span v-if="failedMessage.edited" v-tippy="`Message was edited`" class="label sidebar-label label-info metadata-label">Edited</span>
<span v-if="failedMessage.edited" class="metadata metadata-link"><i class="fa fa-history"></i> <RouterLink :to="routeLinks.failedMessage.message.link(failedMessage.edit_of)">View previous version</RouterLink></span>
<span v-if="failedMessage.time_of_failure" class="metadata"><i class="fa fa-clock-o"></i> Failed: <time-since :date-utc="failedMessage.time_of_failure"></time-since></span>
<span class="metadata"><i class="fa pa-endpoint"></i> Endpoint: {{ failedMessage.receiving_endpoint?.name }}</span>
<span class="metadata"><i class="fa fa-laptop"></i> Machine: {{ failedMessage.receiving_endpoint?.host }}</span>
<span class="metadata"><i class="fa pa-endpoint"></i> Endpoint: {{ failedMessage.receiving_endpoint.name }}</span>
<span class="metadata"><i class="fa fa-laptop"></i> Machine: {{ failedMessage.receiving_endpoint.host }}</span>
<span v-if="failedMessage.redirect" class="metadata"><i class="fa pa-redirect-source pa-redirect-small"></i> Redirect: {{ failedMessage.redirect }}</span>
</div>
<div class="metadata group-message-count message-metadata" v-if="failedMessage.archived">
Expand All @@ -427,7 +425,9 @@ onUnmounted(() => {
<button type="button" class="btn btn-default" v-if="failedMessage.isEditAndRetryEnabled" :disabled="failedMessage.retried || failedMessage.archived || failedMessage.resolved" @click="showEditAndRetryModal()">
<i class="fa fa-pencil"></i> Edit & retry
</button>
<button type="button" class="btn btn-default" @click="debugInServiceInsight()" title="Browse this message in ServiceInsight, if installed"><img src="@/assets/si-icon.svg" /> View in ServiceInsight</button>
<button v-if="!isMassTransitConnected" type="button" class="btn btn-default" @click="debugInServiceInsight()" title="Browse this message in ServiceInsight, if installed">
<img src="@/assets/si-icon.svg" /> View in ServiceInsight
</button>
<button type="button" class="btn btn-default" @click="exportMessage()"><i class="fa fa-download"></i> Export message</button>
</div>
</div>
Expand All @@ -438,7 +438,7 @@ onUnmounted(() => {
<h5 :class="{ active: panel === 1 }" class="nav-item" @click="togglePanel(1)"><a href="javascript:void(0)">Stacktrace</a></h5>
<h5 :class="{ active: panel === 2 }" class="nav-item" @click="togglePanel(2)"><a href="javascript:void(0)">Headers</a></h5>
<h5 :class="{ active: panel === 3 }" class="nav-item" @click="togglePanel(3)"><a href="javascript:void(0)">Message body</a></h5>
<h5 :class="{ active: panel === 4 }" class="nav-item" @click="togglePanel(4)"><a href="javascript:void(0)">Flow Diagram</a></h5>
<h5 v-if="!isMassTransitConnected" :class="{ active: panel === 4 }" class="nav-item" @click="togglePanel(4)"><a href="javascript:void(0)">Flow Diagram</a></h5>
</div>
<pre v-if="panel === 0">{{ failedMessage.exception?.message }}</pre>
<pre v-if="panel === 1">{{ failedMessage.exception?.stack_trace }}</pre>
Expand Down Expand Up @@ -552,4 +552,14 @@ button img {
height: 14px;
width: 14px;
}

.pa-endpoint {
position: relative;
top: 3px;
background-image: url("@/assets/endpoint.svg");
background-position: center;
background-repeat: no-repeat;
height: 15px;
width: 15px;
}
</style>
5 changes: 5 additions & 0 deletions src/Frontend/src/components/failedmessages/PendingRetries.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SortOptions, { SortDirection } from "@/resources/SortOptions";
import QueueAddress from "@/resources/QueueAddress";
import { TYPE } from "vue-toastification";
import GroupOperation from "@/resources/GroupOperation";
import { useIsMassTransitConnected } from "@/composables/connectedApplications";

let refreshInterval: number | undefined;
let sortMethod: SortOptions<GroupOperation> | undefined;
Expand Down Expand Up @@ -49,6 +50,7 @@ const sortOptions: SortOptions<GroupOperation>[] = [
},
];
const periodOptions = ["All Pending Retries", "Retried in the last 2 Hours", "Retried in the last 1 Day", "Retried in the last 7 Days"];
const isMassTransitConnected = useIsMassTransitConnected();

watch(pageNumber, () => loadPendingRetryMessages());

Expand Down Expand Up @@ -246,6 +248,9 @@ onMounted(() => {
<i class="fa fa-external-link fake-link"></i>
</div>
</div>
<div class="col-12" v-if="isMassTransitConnected">
<div class="alert alert-info">MassTransit endpoints currently do not report when a pending retry has succeeded, and therefore any messages associated with those endpoints will need to be manually marked as resolved.</div>
</div>
</div>
<div class="row">
<div class="col-6">
Expand Down
6 changes: 6 additions & 0 deletions src/Frontend/src/components/monitoring/MonitoringHead.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<script setup lang="ts">
import { useIsMassTransitConnected } from "@/composables/connectedApplications";
import MonitoringFilter from "./MonitoringFilter.vue";

const isMassTransitConnected = useIsMassTransitConnected();
</script>

<template>
Expand All @@ -10,6 +13,9 @@ import MonitoringFilter from "./MonitoringFilter.vue";
<div class="col-sm-8 no-side-padding toolbar-menus">
<MonitoringFilter />
</div>
<div class="col-12" v-if="isMassTransitConnected">
<div class="alert alert-info">MassTransit endpoints are currently not supported by monitoring functionality and will not show in this view.</div>
</div>
</div>
</template>

Expand Down
Loading
Loading