diff --git a/src/Frontend/src/components/FilterInput.vue b/src/Frontend/src/components/FilterInput.vue new file mode 100644 index 000000000..55b310db4 --- /dev/null +++ b/src/Frontend/src/components/FilterInput.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/Frontend/src/components/OrderBy.vue b/src/Frontend/src/components/OrderBy.vue index 7bc001116..9d4a9f78a 100644 --- a/src/Frontend/src/components/OrderBy.vue +++ b/src/Frontend/src/components/OrderBy.vue @@ -1,21 +1,8 @@ - - + + + + diff --git a/src/Frontend/src/components/failedmessages/FailedMessageGroups.vue b/src/Frontend/src/components/failedmessages/FailedMessageGroups.vue index fb7949db8..46e01737f 100644 --- a/src/Frontend/src/components/failedmessages/FailedMessageGroups.vue +++ b/src/Frontend/src/components/failedmessages/FailedMessageGroups.vue @@ -8,9 +8,10 @@ import LicenseExpired from "../../components/LicenseExpired.vue"; import ServiceControlNotAvailable from "../ServiceControlNotAvailable.vue"; import LastTenOperations from "../failedmessages/LastTenOperations.vue"; import MessageGroupList, { IMessageGroupList } from "../failedmessages/MessageGroupList.vue"; -import OrderBy, { getSortFunction } from "@/components/OrderBy.vue"; +import OrderBy from "@/components/OrderBy.vue"; import SortOptions, { SortDirection } from "@/resources/SortOptions"; import GroupOperation from "@/resources/GroupOperation"; +import getSortFunction from "@/components/getSortFunction"; const selectedClassifier = ref(""); const classifiers = ref([]); diff --git a/src/Frontend/src/components/getSortFunction.ts b/src/Frontend/src/components/getSortFunction.ts new file mode 100644 index 000000000..5cd7997e3 --- /dev/null +++ b/src/Frontend/src/components/getSortFunction.ts @@ -0,0 +1,20 @@ +import type { GroupPropertyType } from "@/resources/SortOptions"; +import { SortDirection } from "@/resources/SortOptions"; + +export default function getSortFunction(selector: ((group: T) => GroupPropertyType) | undefined, dir: SortDirection) { + if (!selector) { + return () => 0; + } + const sortFunc = (firstElement: T, secondElement: T) => { + const x = selector(firstElement); + const y = selector(secondElement); + if (x > y) { + return 1; + } else if (x < y) { + return -1; + } + return 0; + }; + + return dir === SortDirection.Ascending ? sortFunc : (firstElement: T, secondElement: T) => -sortFunc(firstElement, secondElement); +} diff --git a/src/Frontend/src/components/heartbeats/ActiveEndpoints.vue b/src/Frontend/src/components/heartbeats/ActiveEndpoints.vue deleted file mode 100644 index a3181f6d2..000000000 --- a/src/Frontend/src/components/heartbeats/ActiveEndpoints.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/src/Frontend/src/components/heartbeats/EndpointInstances.vue b/src/Frontend/src/components/heartbeats/EndpointInstances.vue new file mode 100644 index 000000000..2a6577bfc --- /dev/null +++ b/src/Frontend/src/components/heartbeats/EndpointInstances.vue @@ -0,0 +1,250 @@ + + + + + diff --git a/src/Frontend/src/components/heartbeats/EndpointSettingsSupported.vue b/src/Frontend/src/components/heartbeats/EndpointSettingsSupported.vue new file mode 100644 index 000000000..88b9571b1 --- /dev/null +++ b/src/Frontend/src/components/heartbeats/EndpointSettingsSupported.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/Frontend/src/components/heartbeats/HealthyEndpoints.vue b/src/Frontend/src/components/heartbeats/HealthyEndpoints.vue new file mode 100644 index 000000000..956a6bb84 --- /dev/null +++ b/src/Frontend/src/components/heartbeats/HealthyEndpoints.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/Frontend/src/components/heartbeats/HeartbeatConfiguration.vue b/src/Frontend/src/components/heartbeats/HeartbeatConfiguration.vue index 887e9286b..6342f2d64 100644 --- a/src/Frontend/src/components/heartbeats/HeartbeatConfiguration.vue +++ b/src/Frontend/src/components/heartbeats/HeartbeatConfiguration.vue @@ -1,59 +1,158 @@ diff --git a/src/Frontend/src/components/heartbeats/HeartbeatsList.vue b/src/Frontend/src/components/heartbeats/HeartbeatsList.vue new file mode 100644 index 000000000..de47e72ad --- /dev/null +++ b/src/Frontend/src/components/heartbeats/HeartbeatsList.vue @@ -0,0 +1,148 @@ + + + + + diff --git a/src/Frontend/src/components/heartbeats/HeartbeatsMenuItem.vue b/src/Frontend/src/components/heartbeats/HeartbeatsMenuItem.vue index a8589860c..213adff42 100644 --- a/src/Frontend/src/components/heartbeats/HeartbeatsMenuItem.vue +++ b/src/Frontend/src/components/heartbeats/HeartbeatsMenuItem.vue @@ -2,15 +2,16 @@ import { RouterLink } from "vue-router"; import routeLinks from "@/router/routeLinks"; import { useHeartbeatsStore } from "@/stores/HeartbeatsStore"; +import { storeToRefs } from "pinia"; -const store = useHeartbeatsStore(); +const { failedHeartbeatsCount } = storeToRefs(useHeartbeatsStore()); diff --git a/src/Frontend/src/components/heartbeats/InactiveEndpoints.vue b/src/Frontend/src/components/heartbeats/InactiveEndpoints.vue deleted file mode 100644 index 778dd713c..000000000 --- a/src/Frontend/src/components/heartbeats/InactiveEndpoints.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - - - diff --git a/src/Frontend/src/components/heartbeats/LastHeartbeat.vue b/src/Frontend/src/components/heartbeats/LastHeartbeat.vue new file mode 100644 index 000000000..52ce096ff --- /dev/null +++ b/src/Frontend/src/components/heartbeats/LastHeartbeat.vue @@ -0,0 +1,19 @@ + + + diff --git a/src/Frontend/src/components/heartbeats/UnhealthyEndpoints.vue b/src/Frontend/src/components/heartbeats/UnhealthyEndpoints.vue new file mode 100644 index 000000000..4c4c8c2a2 --- /dev/null +++ b/src/Frontend/src/components/heartbeats/UnhealthyEndpoints.vue @@ -0,0 +1,24 @@ + + + diff --git a/src/Frontend/src/components/heartbeats/endpointSettingsClient.ts b/src/Frontend/src/components/heartbeats/endpointSettingsClient.ts new file mode 100644 index 000000000..c8125a0a9 --- /dev/null +++ b/src/Frontend/src/components/heartbeats/endpointSettingsClient.ts @@ -0,0 +1,21 @@ +import { useTypedFetchFromServiceControl } from "@/composables/serviceServiceControlUrls"; + +import { EndpointSettings } from "@/resources/EndpointSettings"; +import isEndpointSettingsSupported from "@/components/heartbeats/isEndpointSettingsSupported"; + +class EndpointSettingsClient { + public async endpointSettings(): Promise { + if (isEndpointSettingsSupported.value) { + const [_, data] = await useTypedFetchFromServiceControl(`endpointssettings`); + return data; + } + + return [this.defaultEndpointSettingsValue()]; + } + + public defaultEndpointSettingsValue() { + return { name: "", track_instances: true }; + } +} + +export default new EndpointSettingsClient(); diff --git a/src/Frontend/src/components/heartbeats/heartbeats.css b/src/Frontend/src/components/heartbeats/heartbeats.css new file mode 100644 index 000000000..05f8b75ff --- /dev/null +++ b/src/Frontend/src/components/heartbeats/heartbeats.css @@ -0,0 +1,91 @@ +.lead.endpoint-details-link.righ-side-ellipsis a { + color: #00729c; + margin: 0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + border-bottom: 1px dotted lightgrey; +} + +.lead.endpoint-details-link.righ-side-ellipsis a:hover { + border-bottom: 1px solid #00729c; + text-decoration: none !important; +} + +.righ-side-ellipsis { + direction: rtl; + text-align: left; +} + +.box-header { + display: flex; + gap: 0.5em; + max-width: 100%; +} + +.endpoint-name > div > div > a { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + color: #00729c; + border-bottom: 1px dotted lightgrey; +} + +.endpoint-name, +.endpoint-name > div { + display: flex; + flex-direction: column; + align-items: flex-start; + flex-wrap: wrap; + justify-content: center; +} + +.endpoint-name { + gap: 0.25em; +} + +.endpoint-name .box-header { + flex-direction: row; + justify-content: flex-start; + align-items: center; + max-width: 100%; +} + +.endpoint-name .box-header > *:not(:first-child) { + margin-left: 0.25em; +} + +.endpoint-count { + font-weight: bold; +} + +p:not(.lead) { + margin: 0 0 5px; +} + +div[role="columnheader"] { + display: flex; +} + +div[role="cell"] { + padding: 10px; + align-items: center; + display: flex; + gap: 0.25em; +} + +div[role="cell"].centre, +div[role="columnheader"].centre { + justify-content: center; +} + +.grid-row { + display: flex; + position: relative; + border-top: 1px solid #eee; + border-right: 1px solid #fff; + border-bottom: 1px solid #eee; + border-left: 1px solid #fff; + background-color: #fff; + margin: 0; +} diff --git a/src/Frontend/src/components/heartbeats/isEndpointSettingsSupported.ts b/src/Frontend/src/components/heartbeats/isEndpointSettingsSupported.ts new file mode 100644 index 000000000..573b90ef7 --- /dev/null +++ b/src/Frontend/src/components/heartbeats/isEndpointSettingsSupported.ts @@ -0,0 +1,8 @@ +import { computed } from "vue"; +import { useIsSupported } from "@/composables/serviceSemVer"; +import { environment } from "@/composables/serviceServiceControl"; + +export const minimumSCVersionForEndpointSettings = "5.9.0"; +const isEndpointSettingsSupported = computed(() => useIsSupported(environment.sc_version, minimumSCVersionForEndpointSettings)); + +export default isEndpointSettingsSupported; diff --git a/src/Frontend/src/components/heartbeats/serviceControlWithHeartbeats.ts b/src/Frontend/src/components/heartbeats/serviceControlWithHeartbeats.ts new file mode 100644 index 000000000..c674aba34 --- /dev/null +++ b/src/Frontend/src/components/heartbeats/serviceControlWithHeartbeats.ts @@ -0,0 +1,14 @@ +import { SetupFactoryOptions } from "../../../test/driver"; +import * as precondition from "../../../test/preconditions"; +import { minimumSCVersionForEndpointSettings } from "@/components/heartbeats/isEndpointSettingsSupported"; + +export const serviceControlWithHeartbeats = async ({ driver }: SetupFactoryOptions) => { + await driver.setUp(precondition.hasUpToDateServicePulse); + await driver.setUp(precondition.hasUpToDateServiceControl); + await driver.setUp(precondition.errorsDefaultHandler); + await driver.setUp(precondition.hasNoFailingCustomChecks); + await driver.setUp(precondition.hasEventLogItems); + await driver.setUp(precondition.hasServiceControlMainInstance(minimumSCVersionForEndpointSettings)); + await driver.setUp(precondition.hasNoDisconnectedEndpoints); + await driver.setUp(precondition.hasServiceControlMonitoringInstance); +}; diff --git a/src/Frontend/src/components/monitoring/EndpointInstances.vue b/src/Frontend/src/components/monitoring/EndpointInstances.vue index dda4d48ba..705ebcfc6 100644 --- a/src/Frontend/src/components/monitoring/EndpointInstances.vue +++ b/src/Frontend/src/components/monitoring/EndpointInstances.vue @@ -112,9 +112,9 @@ onMounted(async () => { - + - + {{ instance.errorCount }} diff --git a/src/Frontend/src/components/monitoring/EndpointListRow.vue b/src/Frontend/src/components/monitoring/EndpointListRow.vue index c211812af..33a19b809 100644 --- a/src/Frontend/src/components/monitoring/EndpointListRow.vue +++ b/src/Frontend/src/components/monitoring/EndpointListRow.vue @@ -18,6 +18,7 @@ import { useMonitoringStore } from "../../stores/MonitoringStore"; import { storeToRefs } from "pinia"; import type { GroupedEndpoint, Endpoint } from "@/resources/MonitoringEndpoint"; import routeLinks from "@/router/routeLinks"; +import { Tippy } from "vue-tippy"; const settings = defineProps<{ endpoint: GroupedEndpoint | Endpoint; @@ -43,9 +44,14 @@ const criticalTimeGraphDuration = computed(() => formatGraphDuration(endpoint.va
({{ endpoint.connectedCount }}/{{ endpoint.connectedCount + endpoint.disconnectedCount }}) formatGraphDuration(endpoint.va @import "./monitoring.css"; @import "./endpoint.css"; +.hackToPreventSafariFromShowingTooltip::after { + content: ""; + display: block; +} + .lead.endpoint-details-link.righ-side-ellipsis { color: #00729c; margin: 0; diff --git a/src/Frontend/src/components/monitoring/MonitoringFilter.vue b/src/Frontend/src/components/monitoring/MonitoringFilter.vue index 1a6773e58..e88a5371e 100644 --- a/src/Frontend/src/components/monitoring/MonitoringFilter.vue +++ b/src/Frontend/src/components/monitoring/MonitoringFilter.vue @@ -1,4 +1,5 @@