Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -3,7 +3,7 @@ import { computed, onMounted, ref, watch } from "vue";

interface MessageHeader {
key: string;
value: string;
value?: string;
isChanged: boolean;
isMarkedAsRemoved: boolean;
isLocked: boolean;
Expand All @@ -14,7 +14,7 @@ const settings = defineProps<{
header: MessageHeader;
}>();

let origHeaderValue: string;
let origHeaderValue: string | undefined;
const header = ref<MessageHeader>(settings.header);

const headerValue = computed(() => settings.header.value);
Expand Down
68 changes: 51 additions & 17 deletions src/Frontend/src/components/failedmessages/EditRetryDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,30 @@ const props = defineProps<{
configuration: EditAndRetryConfig;
}>();

const panel = ref();
const localMessage = ref();
interface LocalMessageState {
isBodyChanged: boolean;
isBodyEmpty: boolean;
isContentTypeSupported: boolean;
bodyContentType: string | undefined;
bodyUnavailable: boolean;
isEvent: boolean;
retried: boolean;
headers: HeaderWithEditing[];
messageBody: string;
}

const panel = ref(0);
const localMessage = ref<LocalMessageState>({
isBodyChanged: false,
isBodyEmpty: false,
isContentTypeSupported: false,
bodyContentType: undefined,
bodyUnavailable: true,
isEvent: false,
retried: false,
headers: [],
messageBody: "",
});
let origMessageBody: string;

const showEditAndRetryConfirmation = ref(false);
Expand Down Expand Up @@ -79,10 +101,12 @@ function findHeadersByKey(key: string) {

function getContentType() {
const header = findHeadersByKey("NServiceBus.ContentType");
return header.value;
return header?.value;
}

function isContentTypeSupported(contentType: string) {
function isContentTypeSupported(contentType: string | undefined) {
if (contentType === undefined) return false;

if (contentType.startsWith("text/")) return true;

const charsetUtf8 = "; charset=utf-8";
Expand All @@ -106,7 +130,7 @@ function isContentTypeSupported(contentType: string) {

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

function removeHeadersMarkedAsRemoved() {
Expand All @@ -127,7 +151,17 @@ async function retryEditedMessage() {

function initializeMessageBodyAndHeaders() {
origMessageBody = props.message.messageBody;
localMessage.value = props.message;
localMessage.value = {
isBodyChanged: false,
isBodyEmpty: false,
isContentTypeSupported: false,
bodyContentType: undefined,
bodyUnavailable: props.message.bodyUnavailable,
isEvent: false,
retried: props.message.retried,
headers: props.message.headers.map((header: Header) => ({ ...header })) as HeaderWithEditing[],
messageBody: props.message.messageBody,
};
localMessage.value.isBodyEmpty = false;
localMessage.value.isBodyChanged = false;

Expand All @@ -136,7 +170,7 @@ function initializeMessageBodyAndHeaders() {
localMessage.value.isContentTypeSupported = isContentTypeSupported(contentType);

const messageIntent = getMessageIntent();
localMessage.value.isEvent = messageIntent.value === "Publish";
localMessage.value.isEvent = messageIntent === "Publish";

for (let index = 0; index < props.message.headers.length; index++) {
const header: HeaderWithEditing = props.message.headers[index] as HeaderWithEditing;
Expand Down Expand Up @@ -191,15 +225,15 @@ onMounted(() => {
</div>
<div class="row msg-editor-content">
<div class="col-sm-12 no-side-padding">
<div class="alert alert-warning" v-if="localMessage?.isEvent">
<div class="alert alert-warning" v-if="localMessage.isEvent">
<div class="col-sm-12">
<i class="fa fa-exclamation-circle"></i> This message is an event. If it was already successfully handled by other subscribers, editing it now has the risk of changing the semantic meaning of the event and may result in
altering the system behavior.
</div>
</div>
<div class="alert alert-warning" v-if="!localMessage?.isContentTypeSupported || localMessage?.bodyUnavailable">
<div class="alert alert-warning" v-if="!localMessage.isContentTypeSupported || localMessage.bodyUnavailable">
<div role="status" aria-label="cannot edit message body" class="col-sm-12">
<i class="fa fa-exclamation-circle"></i> Message body cannot be edited because content type "{{ localMessage?.bodyContentType }}" is not supported. Only messages with content types "application/json" and "text/xml" can be
<i class="fa fa-exclamation-circle"></i> Message body cannot be edited because content type "{{ localMessage.bodyContentType }}" is not supported. Only messages with content types "application/json" and "text/xml" can be
edited.
</div>
</div>
Expand All @@ -208,16 +242,16 @@ onMounted(() => {
</div>
<table role="tabpanel" class="table" v-if="panel === 1">
<tbody>
<tr class="interactiveList" v-for="header in localMessage?.headers" :key="header.key">
<tr class="interactiveList" v-for="header in localMessage.headers" :key="header.key">
<MessageHeader :header="header"></MessageHeader>
</tr>
</tbody>
</table>
<div role="tabpanel" v-if="panel === 2 && !localMessage?.bodyUnavailable" style="height: calc(100% - 260px)">
<textarea aria-label="message body" class="form-control" :disabled="!localMessage?.isContentTypeSupported" v-model="localMessage.messageBody"></textarea>
<span class="empty-error" v-if="localMessage?.isBodyEmpty"><i class="fa fa-exclamation-triangle"></i> Message body cannot be empty</span>
<span class="reset-body" v-if="localMessage?.isBodyChanged"><i class="fa fa-undo" v-tippy="`Reset changes`"></i> <a @click="resetBodyChanges()" href="javascript:void(0)">Reset changes</a></span>
<div class="alert alert-info" v-if="localMessage?.panel === 2 && localMessage.bodyUnavailable">{{ localMessage.bodyUnavailable }}</div>
<div role="tabpanel" v-if="panel === 2 && !localMessage.bodyUnavailable" style="height: calc(100% - 260px)">
<textarea aria-label="message body" class="form-control" :disabled="!localMessage.isContentTypeSupported" v-model="localMessage.messageBody"></textarea>
<span class="empty-error" v-if="localMessage.isBodyEmpty"><i class="fa fa-exclamation-triangle"></i> Message body cannot be empty</span>
<span class="reset-body" v-if="localMessage.isBodyChanged"><i class="fa fa-undo" v-tippy="`Reset changes`"></i> <a @click="resetBodyChanges()" href="javascript:void(0)">Reset changes</a></span>
<div class="alert alert-info" v-if="panel === 2 && localMessage.bodyUnavailable">{{ localMessage.bodyUnavailable }}</div>
</div>
</div>
</div>
Expand All @@ -226,7 +260,7 @@ onMounted(() => {
</div>
<div class="modal-footer" v-if="!showEditAndRetryConfirmation && !showCancelConfirmation">
<button class="btn btn-default" @click="confirmCancel()">Cancel</button>
<button class="btn btn-primary" :disabled="localMessage?.isBodyEmpty || localMessage?.bodyUnavailable" @click="confirmEditAndRetry()">Retry</button>
<button class="btn btn-primary" :disabled="localMessage.isBodyEmpty || localMessage.bodyUnavailable" @click="confirmEditAndRetry()">Retry</button>
</div>
<div class="modal-footer cancel-confirmation" v-if="showCancelConfirmation">
<div>Are you sure you want to cancel? Any changes you made will be lost.</div>
Expand Down
Loading