Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 15 additions & 2 deletions src/Frontend/src/components/ServiceControlAvailable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,27 @@ const connectionState = connectionStore.connectionState;

<template>
<div class="sp-loader" v-if="connectionState.connecting && !connectionState.unableToConnect" />
<div v-else-if="connectionState.forbidden" class="text-center unsupported">
<h1>Access Denied</h1>
<!-- This is here so that the new line does not need to begin with `>.` because that's ugly and we should feel ashamed of committing that -->
<!-- prettier-ignore -->
<p>
You do not have permission to access the ServiceControl instance at
<span id="serviceControlUrl">{{ serviceControlClient.url }}</span>. Please ensure your account has the required permissions.
</p>
<div class="action-toolbar">
<RouterLink :to="routeLinks.configuration.connections.link"><span class="btn btn-default btn-primary whiteText">View Connection Details</span></RouterLink>
</div>
</div>
<ConditionalRender v-else :supported="!connectionState.unableToConnect">
<template #unsupported>
<div class="text-center unsupported">
<h1>Cannot connect to ServiceControl</h1>
<!-- This is here so that the new line does not need to begin with `>.` because that's ugly and we should feel ashamed of committing that -->
<!-- prettier-ignore -->
<p>
ServicePulse is unable to connect to the ServiceControl instance at
<span id="serviceControlUrl">{{ serviceControlClient.url }}</span
>. Please ensure that ServiceControl is running and accessible from your machine.
<span id="serviceControlUrl">{{ serviceControlClient.url }}</span>. Please ensure that ServiceControl is running and accessible from your machine.
</p>
<div class="action-toolbar">
<RouterLink :to="routeLinks.configuration.connections.link"><span class="btn btn-default btn-primary whiteText">View Connection Details</span></RouterLink>
Expand Down
23 changes: 18 additions & 5 deletions src/Frontend/src/components/monitoring/monitoringClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { authFetch } from "@/composables/useAuthenticatedFetch";
import type { Endpoint, EndpointDetails } from "@/resources/MonitoringEndpoint";
import { HttpError } from "@/utils/HttpError";

export interface MetricsConnectionDetails {
Enabled: boolean;
Expand Down Expand Up @@ -36,9 +37,13 @@ class MonitoringClient {
}

public async getMonitoringVersion() {
const [response] = await this.fetchTypedFromMonitoring("");
if (response?.ok) {
return response.headers.get("X-Particular-Version") ?? "";
try {
const [response] = await this.fetchTypedFromMonitoring("");
if (response?.ok) {
return response.headers.get("X-Particular-Version") ?? "";
}
} catch {
// swallow the exception, the default return captures the intent
}
return "";
}
Expand All @@ -49,8 +54,12 @@ class MonitoringClient {
}

public async getMonitoredEndpoints(historyPeriod: number) {
const [, data] = await this.fetchTypedFromMonitoring<Endpoint[]>(`monitored-endpoints?history=${historyPeriod}`);
return data ?? [];
try {
const [, data] = await this.fetchTypedFromMonitoring<Endpoint[]>(`monitored-endpoints?history=${historyPeriod}`);
return data ?? [];
} catch {
return [];
}
}

public async deletedMonitoredEndpoint(endpointName: string, instanceId: string) {
Expand Down Expand Up @@ -96,6 +105,10 @@ class MonitoringClient {
}

const response = await authFetch(`${this.url}${suffix}`);
if (!response.ok) {
throw new HttpError(response.status, response.statusText);
}

const data = await response.json();

return [response, data];
Expand Down
3 changes: 2 additions & 1 deletion src/Frontend/src/components/serviceControlClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { authFetch } from "@/composables/useAuthenticatedFetch";
import { HttpError } from "@/utils/HttpError";

export interface ServiceControlInstanceConnection {
settings: { [key: string]: object };
Expand Down Expand Up @@ -30,7 +31,7 @@ class ServiceControlClient {

public async fetchTypedFromServiceControl<T>(suffix: string, signal?: AbortSignal): Promise<[Response, T]> {
const response = await authFetch(`${this.url}${suffix}`, { signal });
if (!response.ok) throw new Error(response.statusText ?? "No response");
if (!response.ok) throw new HttpError(response.status, response.statusText ?? "No response");
const data = await response.json();

return [response, data];
Expand Down
1 change: 1 addition & 0 deletions src/Frontend/src/resources/ConnectionState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export interface ConnectionState {
connecting: boolean;
connectedRecently: boolean;
unableToConnect: boolean | null;
forbidden: boolean;
}
17 changes: 14 additions & 3 deletions src/Frontend/src/stores/ConnectionsAndStatsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import type { ConnectionState } from "@/resources/ConnectionState";
import { useCounter } from "@vueuse/core";
import monitoringClient from "@/components/monitoring/monitoringClient";
import serviceControlClient from "@/components/serviceControlClient";
import { HttpError } from "@/utils/HttpError";

export const useConnectionsAndStatsStore = defineStore("ConnectionsAndStatsStore", () => {
const isMonitoringEnabled = monitoringClient.isMonitoringEnabled;
Expand All @@ -25,13 +26,15 @@ export const useConnectionsAndStatsStore = defineStore("ConnectionsAndStatsStore
connecting: false,
connectedRecently: false,
unableToConnect: null,
forbidden: false,
});

const monitoringConnectionState = reactive<ConnectionState>({
connected: false,
connecting: false,
connectedRecently: false,
unableToConnect: null,
forbidden: false,
});

const displayConnectionsWarning = computed(() => (connectionState.unableToConnect || (monitoringConnectionState.unableToConnect && isMonitoringEnabled)) ?? false);
Expand Down Expand Up @@ -87,17 +90,25 @@ async function fetchAndSetConnectionState(fetchFunction: () => Promise<number |
try {
const data = await fetchFunction();
connectionState.unableToConnect = false;
connectionState.forbidden = false;
connectionState.connectedRecently = true;
connectionState.connected = true;
connectionState.connecting = false;

return data ?? 0;
} catch (err) {
connectionState.connected = false;
connectionState.unableToConnect = true;
connectionState.connectedRecently = false;
connectionState.connecting = false;
logger.error(err);
connectionState.connectedRecently = false;

if (err instanceof HttpError && err.status === 403) {
connectionState.forbidden = true;
connectionState.unableToConnect = false;
} else {
connectionState.forbidden = false;
connectionState.unableToConnect = true;
logger.error(err);
}
}
} catch {
connectionState.connecting = false;
Expand Down
7 changes: 7 additions & 0 deletions src/Frontend/src/utils/HttpError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class HttpError extends Error {
status: number;
constructor(status: number, message?: string) {
super(message ?? String(status));
this.status = status;
}
}
Loading