(null);
useEffect(() => {
if (!context?.entity?.tag || !context?.apiBaseUrl) {
@@ -62,6 +68,14 @@ const PagerDutyPlugin: React.FC = () => {
void fetchEntityYaml();
}, [fetchEntityYaml, rerender]);
+ if (isConfigured === null) {
+ return ;
+ }
+
+ if (!isConfigured) {
+ return ;
+ }
+
return (
{!isEmpty(entityYaml) && (
diff --git a/plugins/pagerduty-incidents/src/hooks/cortexHooks.tsx b/plugins/pagerduty-incidents/src/hooks/cortexHooks.tsx
new file mode 100644
index 0000000..8f5a906
--- /dev/null
+++ b/plugins/pagerduty-incidents/src/hooks/cortexHooks.tsx
@@ -0,0 +1,196 @@
+import { usePluginContext } from "@cortexapps/plugin-core/components";
+
+import { useErrorToast } from "./uiHooks";
+import { useCallback } from "react";
+
+export interface PluginUpdateFns {
+ doAddSecret: (name: string, secret: string) => Promise;
+ doAddProxy: (
+ name: string,
+ tag: string,
+ secretTag: string
+ ) => Promise;
+ doUpdatePlugin: (
+ pluginTag: string,
+ uiBlobUrl: string,
+ proxyTag: string
+ ) => Promise;
+}
+
+export const usePluginUpdateFns = (): PluginUpdateFns => {
+ const errorToast = useErrorToast();
+ const { apiBaseUrl } = usePluginContext();
+ const apiOrigin = apiBaseUrl ? new URL(apiBaseUrl).origin : "";
+ const internalBaseUrl = apiOrigin ? `${apiOrigin}/api/internal/v1` : "";
+
+ const doAddSecret = useCallback(
+ async (name: string, secret: string): Promise => {
+ if (!internalBaseUrl) {
+ errorToast({
+ title: "Failed to add secret",
+ message: "Internal base URL not available",
+ });
+ return false;
+ }
+ const body = JSON.stringify({
+ name,
+ tag: name,
+ secret,
+ });
+
+ try {
+ const response = await fetch(`${internalBaseUrl}/secrets`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body,
+ });
+
+ if (!response?.ok) {
+ const message =
+ (response as any).cortexResponse?.statusText ||
+ response.statusText ||
+ "An error occurred";
+ throw new Error(message);
+ }
+ return true;
+ } catch (e) {
+ let message = e.message || "An error occurred";
+ try {
+ const data = JSON.parse(e.message);
+ message = data.details || data.message || message;
+ } catch (e) {
+ // Ignore
+ }
+ errorToast({
+ title: "Failed to add secret",
+ message,
+ });
+ }
+ return false;
+ },
+ [internalBaseUrl, errorToast]
+ );
+
+ const doAddProxy = useCallback(
+ async (name: string, tag: string, secretTag: string): Promise => {
+ if (!internalBaseUrl) {
+ errorToast({
+ title: "Failed to add proxy",
+ message: "Internal base URL not available",
+ });
+ return false;
+ }
+ const body = JSON.stringify({
+ name,
+ tag,
+ urlConfigurations: {
+ "https://api.pagerduty.com": {
+ urlHeaders: [
+ {
+ name: "Authorization",
+ value: `Token token={{{secrets.${secretTag}}}}`,
+ },
+ ],
+ },
+ },
+ });
+
+ try {
+ const response = await fetch(`${internalBaseUrl}/proxies`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body,
+ });
+
+ if (!response?.ok) {
+ const message =
+ (response as any).cortexResponse?.statusText ||
+ response.statusText ||
+ "An error occurred";
+ throw new Error(message);
+ }
+ return true;
+ } catch (e) {
+ console.error("Failed to add proxy", e);
+ let message = e.message || "An error occurred";
+ try {
+ const data = JSON.parse(e.message);
+ message = data.details || data.message || message;
+ } catch (e) {
+ // Ignore
+ }
+ errorToast({
+ title: "Failed to add proxy",
+ message,
+ });
+ }
+ return false;
+ },
+ [internalBaseUrl, errorToast]
+ );
+
+ const doUpdatePlugin = useCallback(
+ async (
+ pluginTag: string,
+ uiBlobUrl: string,
+ proxyTag: string
+ ): Promise => {
+ try {
+ let response = await fetch(`${apiBaseUrl}/plugins/${pluginTag}`);
+ if (!response.ok) {
+ throw new Error("Failed to fetch plugin metadata");
+ }
+ const plugin = await response.json();
+ const pluginDescription = plugin.description || "";
+ if (!pluginDescription.includes("plugin-marketplace")) {
+ throw new Error(
+ "This pagerduty-incidents plugin was not installed by the Plugin Marketplace"
+ );
+ }
+ response = await fetch(uiBlobUrl);
+ if (!response.ok) {
+ throw new Error("Failed to fetch plugin UI");
+ }
+ const ui = await response.text();
+ plugin.proxyTag = proxyTag;
+ plugin.blob = ui;
+ response = await fetch(`${apiBaseUrl}/plugins/${pluginTag}`, {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(plugin),
+ });
+ if (!response.ok) {
+ throw new Error("Failed to update plugin");
+ }
+ return true;
+ } catch (e) {
+ console.error("Failed to update plugin", e);
+ let message = e.message || "An error occurred";
+ try {
+ const data = JSON.parse(e.message);
+ message = data.details || data.message || message;
+ } catch (e) {
+ // Ignore
+ }
+ errorToast({
+ title: "Failed to update plugin",
+ message,
+ });
+ }
+ return false;
+ },
+ [apiBaseUrl, errorToast]
+ );
+
+ return {
+ doAddSecret,
+ doAddProxy,
+ doUpdatePlugin,
+ };
+};
diff --git a/plugins/pagerduty-incidents/src/hooks.tsx b/plugins/pagerduty-incidents/src/hooks/pagerDutyHooks.tsx
similarity index 83%
rename from plugins/pagerduty-incidents/src/hooks.tsx
rename to plugins/pagerduty-incidents/src/hooks/pagerDutyHooks.tsx
index 856c638..4049721 100644
--- a/plugins/pagerduty-incidents/src/hooks.tsx
+++ b/plugins/pagerduty-incidents/src/hooks/pagerDutyHooks.tsx
@@ -1,6 +1,5 @@
-import { useState, useEffect, useCallback } from "react";
-import { useToast } from "@chakra-ui/react";
-import { cortexResponseError } from "./util";
+import { useState, useEffect } from "react";
+import { useErrorToastForResponse } from "./uiHooks";
export interface UsePagerDutyServicesReturn {
services: Array>;
@@ -26,43 +25,61 @@ export interface UsePagerDutyOnCallsReturn {
errorMessage: string;
}
-interface ErrorToastProps {
- title?: string;
- message?: string;
-}
+export const useIsPagerDutyTokenValid = (token: string): boolean => {
+ const [isValid, setIsValid] = useState(false);
+
+ useEffect(() => {
+ if (!token) {
+ setIsValid(false);
+ return;
+ }
+
+ const checkPagerDutyAbilities = async (): Promise => {
+ try {
+ const response = await fetch(`https://api.pagerduty.com/abilities`, {
+ headers: {
+ Authorization: `Token token=${token}`,
+ },
+ });
+
+ if (response.ok) {
+ setIsValid(true);
+ } else {
+ setIsValid(false);
+ }
+ } catch (e) {
+ setIsValid(false);
+ }
+ };
+
+ void checkPagerDutyAbilities();
+ }, [token]);
-export const useErrorToast = (): ((props: ErrorToastProps) => void) => {
- const toast = useToast();
- const errorToast = useCallback(
- ({ title = "Error", message = "An error occurred" }: ErrorToastProps) => {
- toast({
- title,
- description: message,
- status: "error",
- duration: 5000,
- isClosable: true,
- });
- },
- [toast]
- );
-
- return errorToast;
+ return isValid;
};
-export const useErrorToastForResponse = (): ((response: Response) => void) => {
- const errorToast = useErrorToast();
- const errorToastForResponse = useCallback(
- (response: Response) => {
- const { status, message } = cortexResponseError(response);
- errorToast({
- title: `HTTP Error ${status}`,
- message,
- });
- },
- [errorToast]
- );
-
- return errorToastForResponse;
+export const useIsPagerDutyConfigured = (): boolean | null => {
+ const [isConfigured, setIsConfigured] = useState(null);
+
+ useEffect(() => {
+ const checkPagerDutyAbilities = async (): Promise => {
+ try {
+ const response = await fetch(`https://api.pagerduty.com/abilities`);
+
+ if (response.ok) {
+ setIsConfigured(true);
+ } else {
+ setIsConfigured(false);
+ }
+ } catch (e) {
+ setIsConfigured(false);
+ }
+ };
+
+ void checkPagerDutyAbilities();
+ }, []);
+
+ return isConfigured;
};
export const usePagerDutyServices = (): UsePagerDutyServicesReturn => {
diff --git a/plugins/pagerduty-incidents/src/hooks/uiHooks.tsx b/plugins/pagerduty-incidents/src/hooks/uiHooks.tsx
new file mode 100644
index 0000000..6f390e1
--- /dev/null
+++ b/plugins/pagerduty-incidents/src/hooks/uiHooks.tsx
@@ -0,0 +1,42 @@
+import { useCallback } from "react";
+import { useToast } from "@chakra-ui/react";
+import { cortexResponseError } from "../util";
+
+interface ErrorToastProps {
+ title?: string;
+ message?: string;
+}
+
+export const useErrorToast = (): ((props: ErrorToastProps) => void) => {
+ const toast = useToast();
+ const errorToast = useCallback(
+ ({ title = "Error", message = "An error occurred" }: ErrorToastProps) => {
+ toast({
+ title,
+ description: message,
+ status: "error",
+ duration: 5000,
+ isClosable: true,
+ });
+ },
+ [toast]
+ );
+
+ return errorToast;
+};
+
+export const useErrorToastForResponse = (): ((response: Response) => void) => {
+ const errorToast = useErrorToast();
+ const errorToastForResponse = useCallback(
+ (response: Response) => {
+ const { status, message } = cortexResponseError(response);
+ errorToast({
+ title: `HTTP Error ${status}`,
+ message,
+ });
+ },
+ [errorToast]
+ );
+
+ return errorToastForResponse;
+};