Skip to content

Commit 98f87cf

Browse files
authored
Merge pull request #199 from OpenWebhook/display-store-config
Display store config
2 parents 9e0d243 + 0f51bac commit 98f87cf

16 files changed

Lines changed: 305 additions & 11 deletions

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"@pluralsight/ps-design-system-button": "^24.1.1",
2222
"@pluralsight/ps-design-system-carousel": "^14.1.1",
2323
"@pluralsight/ps-design-system-core": "^10.0.4",
24+
"@pluralsight/ps-design-system-datawell": "^7.0.11",
25+
"@pluralsight/ps-design-system-dialog": "^15.0.12",
2426
"@pluralsight/ps-design-system-dropdown": "^13.1.1",
2527
"@pluralsight/ps-design-system-emptystate": "^14.1.1",
2628
"@pluralsight/ps-design-system-icon": "^25.4.0",
@@ -32,6 +34,7 @@
3234
"@pluralsight/ps-design-system-navitem": "^6.1.1",
3335
"@pluralsight/ps-design-system-navuser": "^5.0.16",
3436
"@pluralsight/ps-design-system-normalize": "^7.0.4",
37+
"@pluralsight/ps-design-system-position": "^9.1.2",
3538
"@pluralsight/ps-design-system-table": "^17.1.1",
3639
"@pluralsight/ps-design-system-text": "^20.1.27",
3740
"@pluralsight/ps-design-system-textinput": "^12.1.1",
@@ -48,7 +51,8 @@
4851
"react": "^18.2.0",
4952
"react-dom": "^18.2.0",
5053
"react-github-btn": "^1.3.0",
51-
"react-table": "^7.7.0"
54+
"react-table": "^7.7.0",
55+
"use-http": "^1.0.27"
5256
},
5357
"devDependencies": {
5458
"@testing-library/jest-dom": "^5.16.5",

patches/use-http+1.0.27.patch

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
diff --git a/node_modules/use-http/dist/cjs/types.d.ts b/node_modules/use-http/dist/cjs/types.d.ts
2+
index e33cb72..37a643e 100644
3+
--- a/node_modules/use-http/dist/cjs/types.d.ts
4+
+++ b/node_modules/use-http/dist/cjs/types.d.ts
5+
@@ -215,4 +215,5 @@ export declare type NonObjectKeysOf<T> = {
6+
}[keyof T];
7+
export declare type ObjectValuesOf<T extends Record<string, any>> = Exclude<Exclude<Extract<ValueOf<T>, object>, never>, Array<any>>;
8+
export declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
9+
+//@ts-ignore
10+
export declare type Flatten<T> = Pick<T, NonObjectKeysOf<T>> & UnionToIntersection<ObjectValuesOf<T>>;

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const WebhookList = React.lazy(
1212
() => import("./WebhookDisplay/WebhookList.component")
1313
);
1414
import { ApolloProvider } from "@apollo/client";
15-
import { WebhookStoreUrlContext } from "./WebhookStoreUrl/WebhookStoreUrl.context";
15+
import { WebhookStoreUrlContext } from "./NavBar/WebhookStoreUrl/WebhookStoreUrl.context";
1616
import { createApolloClient } from "./apollo.client";
1717
import { useStateInLocalStorage } from "./use-state-with-local-storage.hook";
1818
import { isValidHttpUrl } from "./utils/is-valid-url";

src/ProxyStatus/HelpButton.component.tsx renamed to src/NavBar/ProxyStatus/HelpButton.component.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { useEffect, useRef } from "react";
2-
import helpIcon from "../animations/help.json";
2+
import helpIcon from "../../animations/help.json";
33
import lottie from "lottie-web";
44
import { Label } from "@pluralsight/ps-design-system-text";
5-
import "../animations/animation-container.css";
5+
import "../../animations/animation-container.css";
66

77
const animationName = "help";
88

File renamed without changes.
File renamed without changes.
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import DataWell from "@pluralsight/ps-design-system-datawell";
2+
import Link from "@pluralsight/ps-design-system-link/dist/esm/react";
3+
import { Heading, List, P } from "@pluralsight/ps-design-system-text";
4+
import React from "react";
5+
6+
export const StoreConfigInnerDialog = ({
7+
availableStores,
8+
storageLimit,
9+
defaultTargets,
10+
accessConfig,
11+
userHasAccessToStore,
12+
}: {
13+
availableStores: { url: string; display: string }[];
14+
accessConfig: { type: "public" | "private"; sublabel: string };
15+
storageLimit?: number;
16+
defaultTargets?: string[];
17+
userHasAccessToStore: boolean;
18+
}) => {
19+
return (
20+
<>
21+
<Heading>
22+
<h1>Store Config</h1>
23+
</Heading>
24+
<div style={{ display: "flex" }}>
25+
<DataWell label="Access" subLabel={accessConfig.sublabel}>
26+
{accessConfig.type} {accessConfig.type === "public" ? "⚠️" : null}
27+
</DataWell>
28+
{storageLimit && (
29+
<DataWell label="Storage Limit">{storageLimit}</DataWell>
30+
)}
31+
{defaultTargets && (
32+
<DataWell label="Default Target">
33+
<List>
34+
{defaultTargets.map((target) => (
35+
<List.Item>{target}</List.Item>
36+
))}
37+
</List>
38+
</DataWell>
39+
)}
40+
</div>
41+
42+
{userHasAccessToStore
43+
? "You have access to this store"
44+
: "You don't have access to this store"}
45+
46+
<Heading>
47+
<h1>Your private webhooks stores</h1>
48+
</Heading>
49+
50+
<List>
51+
{availableStores.length > 0
52+
? availableStores.map((store) => (
53+
<List.Item>
54+
<Link>
55+
<a href={store.url}>{store.display}</a>
56+
</Link>
57+
</List.Item>
58+
))
59+
: null}
60+
</List>
61+
62+
<P>
63+
<Link>
64+
<a href="https://www.openwebhook.io/docs/intro/#authentication">
65+
WebhookStore access documentation
66+
</a>
67+
</Link>
68+
</P>
69+
70+
<P>
71+
<Link>
72+
<a href="https://www.openwebhook.io/docs/intro/#%EF%B8%8F-public-organisation-membership">
73+
You don't see your organisation here?
74+
</a>
75+
</Link>
76+
</P>
77+
</>
78+
);
79+
};
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { Label } from "@pluralsight/ps-design-system-text";
2+
import { Below } from "@pluralsight/ps-design-system-position";
3+
4+
import React, { useContext, useEffect, useState } from "react";
5+
import Button from "@pluralsight/ps-design-system-button";
6+
import { StoreConfigInnerDialog } from "./StoreConfigDialog";
7+
import Dialog from "@pluralsight/ps-design-system-dialog";
8+
import useFetch from "use-http";
9+
import { WebhookStoreUrlContext } from "../WebhookStoreUrl/WebhookStoreUrl.context";
10+
import { ACCESS_TOKEN_KEY, IDENTITY_TOKEN_KEY } from "../../local-storage";
11+
import { decodeJWT } from "../../utils/decode-jwt";
12+
13+
export type AuthMetadata =
14+
| { protected: true; protectionRule: "hostname webhook.store" }
15+
| { protected: true; protectionRule: "github-org"; ghOrg: string }
16+
| { protected: false };
17+
18+
export const StoreConfigNavItem = () => {
19+
const [isClicked, setClicked] = useState<boolean>(false);
20+
21+
const [authConfig, setAuthConfig] = useState<AuthMetadata>({
22+
protected: false,
23+
});
24+
const [storeConfig, setStoreConfig] = useState<{
25+
maxNumberOfWebhookPerHost?: number;
26+
defaultTarget?: string[];
27+
userHasAccessToStore: boolean;
28+
}>({ userHasAccessToStore: false });
29+
30+
const { value: webhookStoreUrl } = useContext(WebhookStoreUrlContext);
31+
const accessToken = localStorage.getItem(ACCESS_TOKEN_KEY);
32+
const { get, response } = useFetch(webhookStoreUrl, {
33+
headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : {},
34+
});
35+
const idToken = localStorage.getItem(IDENTITY_TOKEN_KEY);
36+
const identityToken =
37+
idToken &&
38+
decodeJWT<{ name: string; ghOrganisations: string[] }, any>(idToken);
39+
40+
useEffect(() => {
41+
getConfigs();
42+
}, []);
43+
44+
async function getConfigs() {
45+
const initialiseAuthConfig = await get("auth-metadata");
46+
if (response.ok) setAuthConfig(initialiseAuthConfig);
47+
const initialiseStoreConfig = await get("store-metadata");
48+
if (response.ok)
49+
setStoreConfig({ ...initialiseStoreConfig, userHasAccessToStore: true });
50+
}
51+
52+
const accessConfig = describeAccessFromAuthConfig(
53+
authConfig,
54+
webhookStoreUrl
55+
);
56+
const availableStores = identityToken
57+
? [
58+
{
59+
url: `https://${identityToken.payload.name}.github-org.webhook.store/?access_token=${idToken}`,
60+
display: `${identityToken.payload.name}.github-org.webhook.store`,
61+
},
62+
...identityToken.payload.ghOrganisations.map((orgName) => ({
63+
url: `https://${orgName}.github.webhook.store/?access_token=${idToken}`,
64+
display: `${orgName}.github-org.webhook.store`,
65+
})),
66+
]
67+
: [
68+
{
69+
url: "https://github.webhook.store",
70+
display: "github.webhook.store",
71+
},
72+
];
73+
const defaultTargets = storeConfig.defaultTarget;
74+
const storageLimit = storeConfig.maxNumberOfWebhookPerHost;
75+
76+
return (
77+
<Below
78+
show={
79+
<Dialog>
80+
<StoreConfigInnerDialog
81+
accessConfig={accessConfig}
82+
availableStores={availableStores}
83+
defaultTargets={defaultTargets}
84+
storageLimit={storageLimit}
85+
userHasAccessToStore={storeConfig.userHasAccessToStore}
86+
/>
87+
</Dialog>
88+
}
89+
when={isClicked}
90+
>
91+
<Button
92+
appearance={Button.appearances.flat}
93+
onClick={(_) => setClicked(!isClicked)}
94+
>
95+
<Label size={Label.sizes.xSmall}>
96+
Store Config {authConfig.protected ? null : "⚠️"}
97+
</Label>
98+
</Button>
99+
</Below>
100+
);
101+
};
102+
103+
const describeAccessFromAuthConfig = (
104+
authConfig: AuthMetadata,
105+
webhookStoreUrl: string
106+
): { type: "public" | "private"; sublabel: string } => {
107+
if (!authConfig.protected) {
108+
return { type: "public", sublabel: "Anyone with the link" };
109+
}
110+
111+
if (authConfig.protectionRule === "github-org") {
112+
return {
113+
type: "private",
114+
sublabel: `Only members of ${authConfig.ghOrg} on GitHub`,
115+
};
116+
}
117+
118+
if (authConfig.protectionRule === "hostname webhook.store") {
119+
const webhookStoreDomain = new URL(webhookStoreUrl).hostname;
120+
if (webhookStoreDomain.endsWith(".github.webhook.store")) {
121+
const githubUserName =
122+
webhookStoreDomain.split(".")[webhookStoreDomain.split(".").length - 4];
123+
return {
124+
type: "private",
125+
sublabel: `Only Github user ${githubUserName}`,
126+
};
127+
}
128+
if (webhookStoreDomain.endsWith(".github-org.webhook.store")) {
129+
const githubOrgaName =
130+
webhookStoreDomain.split(".")[webhookStoreDomain.split(".").length - 4];
131+
return {
132+
type: "private",
133+
sublabel: `Only members of ${githubOrgaName} on GitHub`,
134+
};
135+
}
136+
return { type: "public", sublabel: "Anyone with the link" };
137+
}
138+
139+
return { type: "public", sublabel: "Anyone with the link" };
140+
};
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React, { useCallback } from "react";
22
import NavUser from "@pluralsight/ps-design-system-navuser";
3-
import { decodeJWT } from "../utils/decode-jwt";
4-
import { ACCESS_TOKEN_KEY, IDENTITY_TOKEN_KEY } from "../local-storage";
3+
import { decodeJWT } from "../../utils/decode-jwt";
4+
import { ACCESS_TOKEN_KEY, IDENTITY_TOKEN_KEY } from "../../local-storage";
55

66
const getIdentityTokenFromStorageAndCleanUrl = (): string | null => {
77
const storedIdentityToken = localStorage.getItem(IDENTITY_TOKEN_KEY);

0 commit comments

Comments
 (0)