Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- name: Set up Node.js
uses: actions/[email protected]
with:
node-version: 21.6.x
node-version: 22.13.x
- name: Build Frontend
run: .\build.ps1
working-directory: src/ServicePulse.Host
Expand Down
4 changes: 2 additions & 2 deletions src/Frontend/src/components/DashboardItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ const props = defineProps<{
</script>

<template>
<RouterLink class="summary-item" :class="{ 'summary-danger': counter > 0, 'summary-info': counter === 0 || !counter }" :to="url">
<RouterLink aria-label="Dashboard Item" class="summary-item" :class="{ 'summary-danger': counter > 0, 'summary-info': counter === 0 || !counter }" :to="url">
<i class="fa fa-3x" :class="props.iconClass"> </i>
<span v-if="counter > 0" class="badge badge-important">{{ counter }}</span>
<span v-if="counter > 0" aria-label="Alert Count" class="badge badge-important">{{ counter }}</span>
<h4>
<slot></slot>
</h4>
Expand Down
2 changes: 1 addition & 1 deletion src/Frontend/src/components/NoData.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const props = defineProps<{
<div class="col-sm-12">
<div class="row box-header">
<div class="col-sm-12">
<p class="lead hard-wrap">{{ props.message }}</p>
<p class="lead hard-wrap" role="status">{{ props.message }}</p>
<slot>
<p>&nbsp;</p>
</slot>
Expand Down
2 changes: 1 addition & 1 deletion src/Frontend/src/components/OnOffSwitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const emit = defineEmits<{ toggle: [] }>();

<template>
<div class="onoffswitch">
<input type="checkbox" :id="`onoffswitch${id}`" :name="`onoffswitch${id}`" class="onoffswitch-checkbox" @click="emit('toggle')" :checked="value ?? false" />
<input type="checkbox" :id="`onoffswitch${id}`" :name="`onoffswitch${id}`" :aria-label="`onoffswitch${id}`" class="onoffswitch-checkbox" @click="emit('toggle')" :checked="value ?? false" />
<label class="onoffswitch-label" :for="`onoffswitch${id}`">
<span class="onoffswitch-inner"></span>
<span class="onoffswitch-switch"></span>
Expand Down
7 changes: 4 additions & 3 deletions src/Frontend/src/components/TimeSince.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import moment from "moment";

const emptyDate = "0001-01-01T00:00:00";

const props = withDefaults(defineProps<{ dateUtc?: string; defaultTextOnFailure?: string }>(), { dateUtc: emptyDate, defaultTextOnFailure: "n/a" });
const props = withDefaults(defineProps<{ dateUtc?: string; defaultTextOnFailure?: string; titleValue?: string }>(), { dateUtc: emptyDate, defaultTextOnFailure: "n/a", titleValue: undefined });

let interval: number | undefined = undefined;

Expand All @@ -15,9 +15,10 @@ function updateText() {
if (props.dateUtc != null && props.dateUtc !== emptyDate) {
const m = moment.utc(props.dateUtc);
text.value = m.fromNow();
title.value = m.local().format("LLLL") + " (local)\n" + m.utc().format("LLLL") + " (UTC)";
title.value = props.titleValue ?? m.local().format("LLLL") + " (local)\n" + m.utc().format("LLLL") + " (UTC)";
} else {
text.value = title.value = props.defaultTextOnFailure;
text.value = props.defaultTextOnFailure;
title.value = props.titleValue ?? props.defaultTextOnFailure;
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Frontend/src/components/heartbeats/EndpointInstances.vue
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,12 @@ async function toggleAlerts(instance: EndpointsView) {
<template #data="{ pageData }">
<div role="rowgroup" aria-label="endpoints">
<div role="row" :aria-label="instance.name" class="row grid-row" v-for="instance in pageData" :key="instance.id">
<div role="cell" aria-label="instance-name" class="col-6 host-name">
<div role="cell" class="col-6 host-name">
<span role="status" class="status-icon">
<i v-if="instance.heartbeat_information?.reported_status !== EndpointStatus.Alive" aria-label="instance dead" class="fa fa-heartbeat text-danger" />
<i v-else aria-label="instance alive" class="fa fa-heartbeat text-success" />
</span>
<span class="lead">{{ instance.host_display_name }}</span>
<span class="lead" aria-label="instance-name">{{ instance.host_display_name }}</span>
</div>
<div role="cell" aria-label="last-heartbeat" class="col-2 last-heartbeat">
<LastHeartbeat :date="instance.heartbeat_information?.last_report_at" tooltip-target="instance" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { healthyEndpoints, filteredHealthyEndpoints } = storeToRefs(store);
<div class="row">
<ResultsCount :displayed="filteredHealthyEndpoints.length" :total="healthyEndpoints.length" />
</div>
<section name="healthy_endpoints">
<section name="healthy_endpoints" aria-label="Healthy Endpoints">
<no-data v-if="healthyEndpoints.length === 0" message="No healthy endpoints"></no-data>
<div v-if="healthyEndpoints.length > 0" class="row">
<div class="col-sm-12 no-side-padding">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ async function toggleDefaultSetting() {
<div class="row">
<ResultsCount :displayed="filteredEndpoints.length" :total="sortedEndpoints.length" />
</div>
<section name="endpoint_configuration">
<section name="endpoint_configuration" aria-label="Endpoint Configuration">
<div class="row">
<div class="col-9 no-side-padding">
<no-data v-if="sortedEndpoints.length === 0" message="Nothing to configure" />
Expand Down
10 changes: 5 additions & 5 deletions src/Frontend/src/components/heartbeats/HeartbeatsList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function endpointHealth(endpoint: LogicalEndpoint) {
</tippy>
</div>
</div>
<div v-if="columns.includes(ColumnNames.InstancesTotal) || columns.includes(ColumnNames.InstancesDown)" role="cell" aria-label="instance-count" class="col-2">
<div v-if="columns.includes(ColumnNames.InstancesTotal) || columns.includes(ColumnNames.InstancesDown)" role="cell" class="col-2">
<tippy :delay="[300, 0]">
<template #content>
<template v-if="endpoint.track_instances">
Expand All @@ -96,15 +96,15 @@ function endpointHealth(endpoint: LogicalEndpoint) {
</template>
<i v-if="endpoint.track_instances" class="fa fa-server" :class="endpointHealth(endpoint)"></i>
<i v-else class="fa fa-sellsy" :class="endpointHealth(endpoint)"></i>&nbsp;
<span class="endpoint-count">{{ store.instanceDisplayText(endpoint) }}</span>
<span class="endpoint-count" aria-label="instance-count">{{ store.instanceDisplayText(endpoint) }}</span>
</tippy>
</div>
<div v-if="columns.includes(ColumnNames.LastHeartbeat)" role="cell" aria-label="last-heartbeat" class="col-2 last-heartbeat">
<LastHeartbeat :date="endpoint.heartbeat_information?.last_report_at" tooltip-target="endpoint" />
</div>
<div v-if="columns.includes(ColumnNames.Tracked)" role="cell" aria-label="tracked-instances" class="col-1 centre">
<tippy v-if="endpoint.track_instances" content="Instances are being tracked" :delay="[1000, 0]">
<i class="fa fa-check text-success"></i>
<tippy v-if="endpoint.track_instances" id="tracked-instance-desc" content="Instances are being tracked" :delay="[1000, 0]">
<i class="fa fa-check text-success" aria-title="Instances are being tracked"></i>
</tippy>
</div>
<div v-if="columns.includes(ColumnNames.TrackToggle)" role="cell" aria-label="tracked-instances" class="col-2 centre">
Expand All @@ -117,7 +117,7 @@ function endpointHealth(endpoint: LogicalEndpoint) {
<tippy content="All instances have alerts muted" :delay="[300, 0]">
<i class="fa fa-bell-slash text-danger" />
</tippy>
<span class="instances-muted">{{ endpoint.muted_count }}</span>
<span class="instances-muted" aria-label="Muted instance count">{{ endpoint.muted_count }}</span>
</template>
<template v-else-if="endpoint.muted_count > 0">
<tippy :content="`${endpoint.muted_count} instance(s) have alerts muted`" :delay="[300, 0]">
Expand Down
4 changes: 2 additions & 2 deletions src/Frontend/src/components/heartbeats/HeartbeatsMenuItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ const { failedHeartbeatsCount } = storeToRefs(useHeartbeatsStore());
</script>

<template>
<RouterLink :to="routeLinks.heartbeats.root">
<RouterLink aria-label="Heartbeats Menu Item" :to="routeLinks.heartbeats.root">
<i class="fa fa-heartbeat icon-white" title="Heartbeats"></i>
<span class="navbar-label">Heartbeats</span>
<span v-if="failedHeartbeatsCount > 0" class="badge badge-important">{{ failedHeartbeatsCount }}</span>
<span v-if="failedHeartbeatsCount > 0" class="badge badge-important" aria-label="Alert Count">{{ failedHeartbeatsCount }}</span>
</RouterLink>
</template>

Expand Down
4 changes: 2 additions & 2 deletions src/Frontend/src/components/heartbeats/LastHeartbeat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ defineProps<{ date?: string; tooltipTarget: string }>();
</script>

<template>
<p v-if="date"><time-since :date-utc="date" default-text-on-failure="unknown" /></p>
<p v-if="date"><time-since :date-utc="date" default-text-on-failure="unknown" title-value="Last Heartbeat" /></p>
<p v-else>
<tippy :delay="[1000, 300]" :interactive="true">
<template #content>
<p>No heartbeat data received for this {{ tooltipTarget }}.</p>
<p>Have you installed and configured the <a target="_blank" href="https://docs.particular.net/monitoring/heartbeats/install-plugin">heartbeats plugin</a> for this {{ tooltipTarget }}?</p>
</template>
<div>No data available</div>
<span title="Last Heartbeat">No data available</span>
</tippy>
</p>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const { unhealthyEndpoints, filteredUnhealthyEndpoints } = storeToRefs(store);
<div class="row">
<ResultsCount :displayed="filteredUnhealthyEndpoints.length" :total="unhealthyEndpoints.length" />
</div>
<section name="unhealthy_endpoints">
<section name="unhealthy_endpoints" aria-label="Unhealthy Endpoints">
<no-data v-if="unhealthyEndpoints.length === 0" message="No unhealthy endpoints"></no-data>
<div v-else class="row">
<div class="col-sm-12 no-side-padding">
Expand Down
7 changes: 3 additions & 4 deletions src/Frontend/src/stores/HeartbeatsStore.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import { EndpointStatus } from "@/resources/Heartbeat";
import { ColumnNames, useHeartbeatsStore } from "@/stores/HeartbeatsStore";

describe("HeartbeatsStore tests", () => {
async function setup(endpoints: EndpointsView[], endpointSettings: EndpointSettings[], preSetup: (driver: Driver) => Promise<void> = () => Promise.resolve()) {
async function setup(endpoints: EndpointsView[], endpointSettings: EndpointSettings[] = [{ name: "", track_instances: true }], preSetup: (driver: Driver) => Promise<void> = () => Promise.resolve()) {
const driver = makeDriverForTests();

await preSetup(driver);
await driver.setUp(serviceControlWithHeartbeats);
await driver.setUp(precondition.hasEndpointSettings(endpointSettings));
await driver.setUp(precondition.hasHeartbeatsEndpoints(endpoints));
await driver.setUp(precondition.hasHeartbeatsEndpoints(endpoints, endpointSettings));

useServiceControlUrls();
await useServiceControl();
Expand All @@ -34,7 +33,7 @@ describe("HeartbeatsStore tests", () => {
}

test("no endpoints", async () => {
const { filteredHealthyEndpoints, filteredUnhealthyEndpoints } = await setup([], []);
const { filteredHealthyEndpoints, filteredUnhealthyEndpoints } = await setup([]);

expect(filteredHealthyEndpoints.value.length).toBe(0);
expect(filteredUnhealthyEndpoints.value.length).toBe(0);
Expand Down
8 changes: 4 additions & 4 deletions src/Frontend/src/views/HeartbeatsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,21 @@ const isMassTransitConnected = useIsMassTransitConnected();
</div>
<div class="row">
<div class="col-sm-12">
<div class="tabs">
<div class="tabs" role="tablist">
<div>
<!--Inactive Endpoints-->
<h5 :class="{ active: isRouteSelected(routeLinks.heartbeats.unhealthy.link) }">
<RouterLink :to="routeLinks.heartbeats.unhealthy.link"> Unhealthy Endpoints ({{ unhealthyEndpoints.length }}) </RouterLink>
<RouterLink role="tab" :aria-selected="isRouteSelected(routeLinks.heartbeats.unhealthy.link)" :to="routeLinks.heartbeats.unhealthy.link"> Unhealthy Endpoints ({{ unhealthyEndpoints.length }}) </RouterLink>
</h5>

<!--Active Endpoints-->
<h5 :class="{ active: isRouteSelected(routeLinks.heartbeats.healthy.link) }">
<RouterLink :to="routeLinks.heartbeats.healthy.link"> Healthy Endpoints ({{ healthyEndpoints.length }}) </RouterLink>
<RouterLink role="tab" :aria-selected="isRouteSelected(routeLinks.heartbeats.healthy.link)" :to="routeLinks.heartbeats.healthy.link"> Healthy Endpoints ({{ healthyEndpoints.length }}) </RouterLink>
</h5>

<!--Configuration-->
<h5 :class="{ active: isRouteSelected(routeLinks.heartbeats.configuration.link) }">
<RouterLink :to="routeLinks.heartbeats.configuration.link"> Configuration </RouterLink>
<RouterLink role="tab" :aria-selected="isRouteSelected(routeLinks.heartbeats.configuration.link)" :to="routeLinks.heartbeats.configuration.link"> Configuration </RouterLink>
</h5>
</div>
<div class="filter-group">
Expand Down
22 changes: 2 additions & 20 deletions src/Frontend/test/mocks/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { setupWorker } from "msw/browser";
import { Driver } from "../driver";
import { makeMockEndpoint, makeMockEndpointDynamic } from "../mock-endpoint";
import * as precondition from "../preconditions";

export const worker = setupWorker();
const mockEndpoint = makeMockEndpoint({ mockServer: worker });
const mockEndpointDynamic = makeMockEndpointDynamic({ mockServer: worker });
Expand All @@ -24,24 +25,5 @@ const driver = makeDriver();

(async () => {
await driver.setUp(precondition.serviceControlWithMonitoring);
//override the default mocked endpoints with a custom list
await driver.setUp(
precondition.monitoredEndpointsNamed([
"Universe.Solarsystem.Mercury.Endpoint1",
"Universe.Solarsystem.Mercury.Endpoint2",
"Universe.Solarsystem.Venus.Endpoint3",
"Universe.Solarsystem.Venus.Endpoint4",
"Universe.Solarsystem.Earth.Endpoint5",
"Universe.Solarsystem.Earth.Endpoint6",
])
);

await driver.setUp(
precondition.hasFailedMessage({
withGroupId: "81dca64e-76fc-e1c3-11a2-3069f51c58c8",
withMessageId: "40134401-bab9-41aa-9acb-b19c0066f22d",
withContentType: "application/json",
withBody: { Index: 0, Data: "" },
})
);
await driver.setUp(precondition.HasHealthyAndUnHealthyEndpoints(1, 1));
})();
20 changes: 20 additions & 0 deletions src/Frontend/test/mocks/heartbeat-endpoint-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { EndpointsView } from "@/resources/EndpointView";
import { EndpointStatus } from "@/resources/Heartbeat";

export const healthyEndpointTemplate = <EndpointsView>{
is_sending_heartbeats: true,
id: "HealthyEndpoint",
name: "HealthyEndpoint",
monitor_heartbeat: true,
host_display_name: "HealhtyEndpoint.Hostname",
heartbeat_information: { reported_status: EndpointStatus.Alive, last_report_at: new Date().toISOString() },
};

export const unHealthyEndpointTemplate = <EndpointsView>{
is_sending_heartbeats: true,
id: "UnHealthyEndpoint",
name: `UnHealthyEndpoint`,
monitor_heartbeat: true,
host_display_name: "UnHealhtyEndpoint.Hostname",
heartbeat_information: { reported_status: EndpointStatus.Dead, last_report_at: new Date().toISOString() },
};
Loading