Skip to content

Commit b6fabc5

Browse files
warwickschroederjasontaylordev
authored andcommitted
WIP - Clean and change the way config is grabbed
1 parent f6792ba commit b6fabc5

File tree

9 files changed

+251
-143
lines changed

9 files changed

+251
-143
lines changed

src/Frontend/src/components/platformcapabilities/CapabilityCard.vue

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const props = defineProps<{
2323
:class="{
2424
'capability-available': !props.isLoading && props.status === CapabilityStatus.Available,
2525
'capability-unavailable': !props.isLoading && props.status === CapabilityStatus.Unavailable,
26+
'capability-partially-unavailable': !props.isLoading && props.status === CapabilityStatus.PartiallyUnavailable,
2627
'capability-loading': props.isLoading,
2728
'capability-notconfigured': !props.isLoading && props.status === CapabilityStatus.NotConfigured,
2829
}"
@@ -38,6 +39,7 @@ const props = defineProps<{
3839
:class="{
3940
'text-success': props.status === CapabilityStatus.Available,
4041
'text-danger': props.status === CapabilityStatus.Unavailable,
42+
'text-warning': props.status === CapabilityStatus.PartiallyUnavailable,
4143
'text-info': props.status === CapabilityStatus.NotConfigured,
4244
}"
4345
/>
@@ -51,7 +53,7 @@ const props = defineProps<{
5153
class="indicator-light"
5254
:class="{
5355
'light-success': indicator.status === CapabilityStatus.Available,
54-
'light-warning': indicator.status === CapabilityStatus.NotConfigured,
56+
'light-warning': indicator.status === CapabilityStatus.NotConfigured || indicator.status === CapabilityStatus.PartiallyUnavailable,
5557
'light-danger': indicator.status === CapabilityStatus.Unavailable,
5658
}"
5759
/>
@@ -67,6 +69,7 @@ const props = defineProps<{
6769
:class="{
6870
'status-available': props.status === CapabilityStatus.Available,
6971
'status-unavailable': props.status === CapabilityStatus.Unavailable,
72+
'status-partially-unavailable': props.status === CapabilityStatus.PartiallyUnavailable,
7073
}"
7174
>
7275
{{ props.status }}
@@ -104,6 +107,10 @@ const props = defineProps<{
104107
border-left: 4px solid var(--danger-color, #dc3545);
105108
}
106109
110+
.capability-partially-unavailable {
111+
border-left: 4px solid var(--warning-color, #ffc107);
112+
}
113+
107114
.capability-loading {
108115
border-left: 4px solid var(--border-color, #e0e0e0);
109116
}
@@ -118,6 +125,10 @@ const props = defineProps<{
118125
color: #007bff;
119126
}
120127
128+
.text-warning {
129+
color: var(--warning-color, #ffc107);
130+
}
131+
121132
.loading-overlay {
122133
position: absolute;
123134
top: 0;
@@ -252,6 +263,11 @@ const props = defineProps<{
252263
color: #721c24;
253264
}
254265
266+
.status-partially-unavailable {
267+
background-color: #fff3cd;
268+
color: #856404;
269+
}
270+
255271
.capability-footer {
256272
display: flex;
257273
justify-content: space-between;

src/Frontend/src/components/platformcapabilities/PlatformCapabilitiesDashboardItem.vue

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
<script setup lang="ts">
22
import CapabilityCard from "@/components/platformcapabilities/CapabilityCard.vue";
3-
import useThroughputStoreAutoRefresh from "@/composables/useThroughputStoreAutoRefresh";
4-
import { storeToRefs } from "pinia";
53
import { useAuditingCapability } from "@/components/platformcapabilities/capabilities/AuditingCapability";
64
import { useMonitoringCapability } from "@/components/platformcapabilities/capabilities/MonitoringCapability";
75
import { Capability } from "@/components/platformcapabilities/types";
86
9-
const { store } = useThroughputStoreAutoRefresh();
10-
const { testResults } = storeToRefs(store);
11-
const auditing = useAuditingCapability(testResults);
7+
const auditing = useAuditingCapability();
128
const monitoring = useMonitoringCapability();
139
</script>
1410

@@ -26,7 +22,7 @@ const monitoring = useMonitoringCapability();
2622
:icon="auditing.icon.value"
2723
:description="auditing.description.value"
2824
:indicators="auditing.indicators.value"
29-
:isLoading="testResults === null"
25+
:isLoading="auditing.isLoading.value"
3026
help-url="https://docs.particular.net/nservicebus/operations/auditing"
3127
data-url="#/messages"
3228
></CapabilityCard>
@@ -37,7 +33,7 @@ const monitoring = useMonitoringCapability();
3733
:icon="monitoring.icon.value"
3834
:description="monitoring.description.value"
3935
:indicators="monitoring.indicators.value"
40-
:isLoading="testResults === null"
36+
:isLoading="monitoring.isLoading.value"
4137
help-url="https://docs.particular.net/monitoring/metrics/install-plugin"
4238
data-url="#/monitoring"
4339
></CapabilityCard>
Lines changed: 108 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,123 @@
1-
import { computed, type Ref, watchEffect } from "vue";
1+
import { computed, watchEffect } from "vue";
22
import { CapabilityStatus, StatusIndicator } from "@/components/platformcapabilities/types";
3-
import { faCheck, faInfoCircle, faTimes, type IconDefinition } from "@fortawesome/free-solid-svg-icons";
4-
import type ConnectionTestResults from "@/resources/ConnectionTestResults";
5-
import useIsAllMessagesSupported from "@/components/audit/isAllMessagesSupported";
3+
import useIsAllMessagesSupported, { minimumSCVersionForAllMessages } from "@/components/audit/isAllMessagesSupported";
64
import { storeToRefs } from "pinia";
75
import { useAuditStore } from "@/stores/AuditStore";
8-
import { AuditingCardDescription, AuditingIndicatorTooltip } from "@/components/platformcapabilities/constants";
96
import { MessageStatus } from "@/resources/Message";
7+
import { type CapabilityComposable, useCapabilityBase } from "./BaseCapability";
8+
import { useRemoteInstancesStore } from "@/stores/RemoteInstancesStore";
9+
import { RemoteInstanceStatus, type RemoteInstance } from "@/resources/RemoteInstance";
10+
11+
enum AuditingCardDescription {
12+
NotConfigured = "Auditing instance is connected but no successful messages have been processed yet or you don't have auditing enabled for any endpoints.",
13+
Unavailable = "All Auditing instances are configured but not responding.",
14+
PartiallyUnavailable = "Some Auditing instances are not responding. Check individual instance status below.",
15+
NotSupported = `Auditing instance is connected but the "All Messages" feature requires ServiceControl ${minimumSCVersionForAllMessages} or higher.`,
16+
Available = "Auditing is available and processing successful messages.",
17+
}
18+
19+
enum AuditingIndicatorTooltip {
20+
InstanceAvailable = "Auditing instance is configured and available",
21+
InstanceUnavailable = "The Auditing instance is configured but not responding",
22+
MessagesAvailable = "Successful messages are being processed",
23+
MessagesUnavailable = "No successful messages have been processed yet or auditing is not enabled for any endpoints",
24+
AllMessagesNotSupported = `The 'All Messages' feature requires ServiceControl ${minimumSCVersionForAllMessages} or higher`,
25+
}
1026

11-
// http://localhost:33333/api/configuration/remotes
1227
/**
13-
[
14-
{
15-
"api_uri": "http://servicecontrol-audit:44444",
16-
"version": "6.7.6",
17-
"status": "online",
18-
"configuration": {
19-
"host": {
20-
"instance_name": "Particular.ServiceControl.Audit",
21-
"logging": {
22-
"log_path": "/app/.logs",
23-
"logging_level": "information"
24-
}
25-
},
26-
"data_retention": {
27-
"audit_retention_period": "7.00:00:00"
28-
},
29-
"performance_tunning": {
30-
"max_body_size_to_store": 102400
31-
},
32-
"transport": {
33-
"transport_type": "RabbitMQ.QuorumConventionalRouting",
34-
"audit_log_queue": "audit.log",
35-
"audit_queue": "audit",
36-
"forward_audit_messages": false
37-
},
38-
"peristence": {
39-
"persistence_type": "RavenDB"
40-
},
41-
"plugins": {
28+
* Checks if all audit remote instances are unavailable
29+
*/
30+
function allAuditInstancesUnavailable(instances: RemoteInstance[] | null | undefined): boolean {
31+
if (!instances || instances.length === 0) {
32+
return false;
33+
}
34+
return instances.every((instance) => instance.status !== RemoteInstanceStatus.Online);
35+
}
4236

43-
}
44-
}
37+
/**
38+
* Checks if any audit remote instances are unavailable (but not all)
39+
*/
40+
function hasUnavailableAuditInstances(instances: RemoteInstance[] | null | undefined): boolean {
41+
if (!instances || instances.length === 0) {
42+
return false;
4543
}
46-
]
44+
return instances.some((instance) => instance.status !== RemoteInstanceStatus.Online);
45+
}
46+
47+
/**
48+
* Checks if any audit remote instances are available
4749
*/
50+
function hasAvailableAuditInstances(instances: RemoteInstance[] | null | undefined): boolean {
51+
if (!instances || instances.length === 0) {
52+
return false;
53+
}
54+
return instances.some((instance) => instance.status === RemoteInstanceStatus.Online);
55+
}
4856

49-
export function useAuditingCapability(testResults: Ref<ConnectionTestResults | null>) {
57+
/**
58+
* Checks if some but not all audit instances are unavailable
59+
*/
60+
function hasPartiallyUnavailableAuditInstances(instances: RemoteInstance[] | null | undefined): boolean {
61+
if (!instances || instances.length === 0) {
62+
return false;
63+
}
64+
return hasUnavailableAuditInstances(instances) && hasAvailableAuditInstances(instances);
65+
}
66+
67+
export function useAuditingCapability(): CapabilityComposable {
68+
const { getIconForStatus, createIndicator } = useCapabilityBase();
69+
70+
// This gives us the list of remote instances configured in ServiceControl.
71+
const remoteInstancesStore = useRemoteInstancesStore();
72+
const { remoteInstances } = storeToRefs(remoteInstancesStore);
73+
74+
// This gives us the messages array which includes all messages (successful and failed).
5075
const auditStore = useAuditStore();
51-
// this gives us the messages array which includes all messages (successful and failed)
5276
const { messages } = storeToRefs(auditStore);
53-
const isAllMessagesSupported = useIsAllMessagesSupported();
54-
55-
// Count only successful audit messages
5677
const successfulMessageCount = computed(() => {
5778
return messages.value.filter((msg) => msg.status === MessageStatus.Successful || msg.status === MessageStatus.ResolvedSuccessfully).length;
5879
});
5980

60-
// this tells us if the audit instance is configured and responding
61-
const auditingConfiguredAndResponding = computed(() => {
62-
return testResults.value?.audit_connection_result?.connection_successful ?? false;
63-
});
81+
const isAllMessagesSupported = useIsAllMessagesSupported();
6482

6583
watchEffect(() => {
6684
// Trigger initial load of audit messages if audit is configured
67-
if (auditingConfiguredAndResponding.value && messages.value.length === 0) {
85+
if (hasAvailableAuditInstances(remoteInstances.value)) {
6886
// TODO: This is not auto refreshed. User will need to manually refresh the page to get updated data. Ideally this would auto refresh periodically.
6987
auditStore.refresh();
7088
}
7189
});
7290

91+
// Determine overall auditing status
7392
const auditStatus = computed(() => {
74-
// If audit instance is not configured or not responding
75-
if (!auditingConfiguredAndResponding.value) {
93+
// 1. Check if there are any audit instances configured.
94+
if (!remoteInstances.value || remoteInstances.value.length === 0) {
95+
return CapabilityStatus.NotConfigured;
96+
}
97+
98+
// 2. Check if all audit instances are unavailable
99+
if (allAuditInstancesUnavailable(remoteInstances.value)) {
76100
return CapabilityStatus.Unavailable;
77101
}
78102

79-
// If audit instance is available but 'All Messages' feature is not supported or there are no successful audit messages
103+
// 3. Check if some but not all audit instances are unavailable
104+
if (hasPartiallyUnavailableAuditInstances(remoteInstances.value)) {
105+
return CapabilityStatus.PartiallyUnavailable;
106+
}
107+
108+
// 4. Check if all messages feature is supported and there are successful messages
80109
if (!isAllMessagesSupported.value || successfulMessageCount.value === 0) {
81110
return CapabilityStatus.NotConfigured;
82111
}
83112

84-
// Audit instance is available and there are successful audit messages
113+
// 5. Audit instance is available and there are successful audit messages
85114
return CapabilityStatus.Available;
86115
});
87116

88-
const auditIcon = computed<IconDefinition>(() => {
89-
if (auditStatus.value === CapabilityStatus.NotConfigured) {
90-
return faInfoCircle;
91-
}
92-
93-
if (auditStatus.value === CapabilityStatus.Available) {
94-
return faCheck;
95-
}
96-
97-
// Uavailable
98-
return faTimes;
99-
});
117+
// Determine icon based on status
118+
const auditIcon = computed(() => getIconForStatus(auditStatus.value));
100119

120+
// Determine description based on status
101121
const auditDescription = computed(() => {
102122
if (auditStatus.value === CapabilityStatus.NotConfigured) {
103123
return AuditingCardDescription.NotConfigured;
@@ -107,21 +127,31 @@ export function useAuditingCapability(testResults: Ref<ConnectionTestResults | n
107127
return AuditingCardDescription.Available;
108128
}
109129

110-
// Uavailable
130+
if (auditStatus.value === CapabilityStatus.PartiallyUnavailable) {
131+
return AuditingCardDescription.PartiallyUnavailable;
132+
}
133+
134+
// Unavailable
111135
return AuditingCardDescription.Unavailable;
112136
});
113137

114-
const auditIndicators = computed<StatusIndicator[]>(() => {
138+
// Determine indicators
139+
const auditIndicators = computed(() => {
115140
const indicators: StatusIndicator[] = [];
116141

117-
indicators.push({
118-
label: "Instance",
119-
status: auditingConfiguredAndResponding.value ? CapabilityStatus.Available : CapabilityStatus.Unavailable,
120-
tooltip: auditingConfiguredAndResponding.value ? AuditingIndicatorTooltip.InstanceAvailable : AuditingIndicatorTooltip.InstanceUnavailable,
121-
});
142+
// Add an indicator for each remote audit instance
143+
if (remoteInstances.value && remoteInstances.value.length > 0) {
144+
remoteInstances.value.forEach((instance, index) => {
145+
const isAvailable = instance.status === RemoteInstanceStatus.Online;
146+
const label = remoteInstances.value!.length > 1 ? `Instance ${index + 1}` : "Instance";
147+
const tooltip = isAvailable ? AuditingIndicatorTooltip.InstanceAvailable : AuditingIndicatorTooltip.InstanceUnavailable;
122148

123-
// Messages available indicator
124-
if (auditingConfiguredAndResponding.value) {
149+
indicators.push(createIndicator(label, isAvailable ? CapabilityStatus.Available : CapabilityStatus.Unavailable, tooltip, instance.api_uri, instance.version));
150+
});
151+
}
152+
153+
// Messages available indicator - show if at least one instance is available
154+
if (hasAvailableAuditInstances(remoteInstances.value)) {
125155
const messagesAvailable = isAllMessagesSupported.value && successfulMessageCount.value > 0;
126156

127157
let messageTooltip = "";
@@ -133,20 +163,20 @@ export function useAuditingCapability(testResults: Ref<ConnectionTestResults | n
133163
messageTooltip = AuditingIndicatorTooltip.MessagesUnavailable;
134164
}
135165

136-
indicators.push({
137-
label: "Messages",
138-
status: messagesAvailable ? CapabilityStatus.Available : CapabilityStatus.NotConfigured,
139-
tooltip: messageTooltip,
140-
});
166+
indicators.push(createIndicator("Messages", messagesAvailable ? CapabilityStatus.Available : CapabilityStatus.NotConfigured, messageTooltip));
141167
}
142168

143169
return indicators;
144170
});
145171

172+
// Loading state - true if remote instances haven't been loaded yet
173+
const isLoading = computed(() => remoteInstances.value === null || remoteInstances.value === undefined);
174+
146175
return {
147176
status: auditStatus,
148177
icon: auditIcon,
149178
description: auditDescription,
150179
indicators: auditIndicators,
180+
isLoading,
151181
};
152182
}

0 commit comments

Comments
 (0)