From db28536ea805ebe795794eeea5bd0a609991eb8e Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Tue, 13 Jun 2023 11:12:12 +0200 Subject: [PATCH 01/11] feat: add clouflare pages button to integrations page --- backend/src/variables/integration.ts | 11 +++++++++++ .../public/images/integrations/Cloudflare.png | Bin 0 -> 4384 bytes 2 files changed, 11 insertions(+) create mode 100644 frontend/public/images/integrations/Cloudflare.png diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index 26e9f2544..8935e7b49 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -23,6 +23,7 @@ export const INTEGRATION_CIRCLECI = "circleci"; export const INTEGRATION_TRAVISCI = "travisci"; export const INTEGRATION_SUPABASE = 'supabase'; export const INTEGRATION_CHECKLY = 'checkly'; +export const INTEGRATION_CLOUDFLARE_PAGES = 'cloudflare-pages'; export const INTEGRATION_HASHICORP_VAULT = 'hashicorp-vault'; export const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, @@ -65,6 +66,7 @@ export const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; export const INTEGRATION_TRAVISCI_API_URL = "https://api.travis-ci.com"; export const INTEGRATION_SUPABASE_API_URL = 'https://api.supabase.com'; export const INTEGRATION_CHECKLY_API_URL = 'https://api.checklyhq.com'; +export const INTEGRATION_CLOUDFLARE_PAGES_API_URL = 'https://api.cloudflare.com'; export const getIntegrationOptions = async () => { const INTEGRATION_OPTIONS = [ @@ -221,6 +223,15 @@ export const getIntegrationOptions = async () => { type: '', clientId: '', docsLink: '' + }, + { + name: 'Cloudflare Pages', + slug: 'cloudflare-pages', + image: 'Cloudflare.png', + isAvailable: true, + type: 'pat', + clientId: '', + docsLink: '' } ] diff --git a/frontend/public/images/integrations/Cloudflare.png b/frontend/public/images/integrations/Cloudflare.png new file mode 100644 index 0000000000000000000000000000000000000000..24db8c68134e5a9d2d5e3871ba84acb4a68c8e12 GIT binary patch literal 4384 zcmd5EA`8?10yytn|_q}UtZ4TKlwI2WgKoD0TV1W1+7f)e?4;eIZv|!T|uV_^t>6a-T{90EgZqOpP3)p3XC) zKOJQp>s)?uCB(?g_K5ZsGgE9WEHvFnP{=?DYw0-S7)^EdKTxL{>}S7xD3vO;$o2Z1 zY8Oyie@|-IHQTvPZ0yIiHMBwMkI{)`_oe-j3BJ?>QVl9$Tm1yh35lfcXrf#t@n%Wx+jH#d2s{8FOBN*~*!i$nS7=Ow zcaD5}8vR&?jVy6;AZ5Ndz90xCFD0oIy++bULNemc0JI9Mnvw`3tpn756YeiKYb$W^ zLbmUdbq9SZ?^+9}L)Y1NYHJP;thzU}adg>0U{V_)l5AI7b8H~Hj`u2pHC{53RB+{t zUFW;E5toKTSY?RqvK22Nuz_627*U z&2#%g$G8A`IpoDl!bIzh(t*_s&a1?1&B{+YNrl3WpHoIsJbiQ$YKGotNLvzXQnC{O z?;JonN{rwN{SV6)V5X%s=KAU_A_URP(OnQ2uV&DK636^m2W`V4fu>l)gj=)DowCcn zm0Xu7Bo|mSTr5W1K8EKp@j8u`k~rqqCSP*#ER{!k$@ffb&QD$$G@kev`UrWfNsH^c z=WMCjMO#CE%u74{dGQ!4p_~xb@)A^X#QWhjLH1c+|H9Zh>RK2p{`pAWKVNV`bM?X{ z7HNbts}00nv#&S3=d?j3m%$tEz`T1edgO)c5Oa9YpC{n_{b62yyu2B`43OD0l$sli zIcLYEm2*>R(}@}WLN+0+<07@X;!}fYa>5*5q^>u)oN3^R5x0xy*$~(6(YT4P{8@5A z7@(ctjD5hnYYCTn+@tWTO~hUk-mm*u0hV}S3^d*1W_qOT-C5w^G7FvZnZMT&Ou2n$MQBgC2_=lrb z5IpYb*_rkjr&!@TeAYh!HyZowFs?E*`XOxuy68FHFai4ljtM5}Pb9M#OFnM}F&=md zvDbLnt?0vay}(YvRZ_Zw;~piP&4{y!k20*vK+Q6E;emx0HK;CKmmg2jpqM8T?-ZcV z2lCdG!_+G}#`Z79Aq8r+y8FI<+y-`kEMe}{2r480I;q~(N%w~K`K@(uT!4AE1M!#U zwr-`?0X~odo;?%MTNf!1d{nnINlZXjJ;=Os+@$L+*tal$L2otj-ZymZ+TpOtb}D;j%h6Pmqbn9ZXPGmzXAM=Z zmvS__811$AxAzVJbNwDTyEJQ8GlfRh{jghbte);1cj`)n&5>cT3F_QECoRq;#B?N% zY=Zv|H;~Qg{-$5M2DL8%H_?8?sh;)~Y*y2}7Bx%WC7-JlU@@MDeLnvo1i%}E#$2%t zqp!%CSdy#9vM@H_rqLR*ueJR|cadHF5H01iR=s1~*OJ$aB({Y%(#pR0$Xeu7Z*oj^ zkPelU`ohn%3SZ-S&erf-I~7Y;#%AW0avhp$^R>|fhha|%hhlaU-Q5N1iO41nW)bVX zuF_lR%kc`+w8vNblN}`c{=@0oHfcsx)z=Wi;=4P@LzJ3tj8OIkvd8%5o0~T^hfy|C z&d#PTH?4NEv1-Y#PnKKB3qIN=^OdNcnEb~nu-_RlgIXnS`w5E+3GH1Oy9qz|O@+xg z_gFkwn%;_nYjMUGlgq&yF+gydN}2@2ZB0tT;e;rK%~#HfB+H5g<@u$9ebI*A|54+= z0|0t=8WDBfV7iqo=l*)#<0xl*JGmI#R7mLEVaMXa_KnR0yJUkOb&+4a?Ng2Mxy19}7YR+ca5YCLqEIaZGw4{uXL)5Rfbu%fy=hv41-z}%CP zgvgjjytUEz2fy4ku0kW4i0{oF-Vj8U%+FRu;{@WOqJ`OQpPYf`&XzjU=Ut@NKH#cV&&;hBL_tX!E@m#5FG7Y)9D7KkZ4 zT>6=V?YTUQmgXM-CMi{2tP!nRh^D5%mYjj$bd~fnZMVhIG4*4TTK>A~Gmms9@tL|# z@>0%$riLHkhs&pr$1xs+h(yl`WtpAJ3euCYg+|M+J|zm9)3YLQuZHRihcz^uU$D~@ zWj>BwxZjw00sY--%M2AmHWyXXE3)1+Q4P437hOVZd*CBbt?4HItZZ`VhZJs`|!MMVyD9s$_h1`IMW{xa3!z z$&T&3TS!NrLBZ#}C;U!MU@=dAem2Jr*8Q^n<}T@Y9lf5txHB*AsPyxxxh47pKNB&z ztMV~Velp?^pFAM=OdY#l6}qBoGDXdt4z|KDi@H)rG*^7D52lpMKA0B8=D@xXGt56a ztZ~aIoY=OAQ&SrR>zDkHYw+}Zx2rcZL6mP0HOV4p!v!gsu3;GNd;T#=#pnoYSNNNh zo0$hfn#n~A3sS@FPC=uYw=ay>Xlqu%oDoYyI(y(3eBRm-Da6zwI^bUd~4%j{CO(+VAxseb#nau3& zfl=hvaOp`dWa7*{4C>1<#cJ+)ZT)SH}lsTj_5mX4-wT({?oIBIpv8=WgP zhpV6~kFzI05{K+|j?$RF=^*g`;YLE*=jv9Fnny=1pH(hcbw?-7oT$HH4p)F)gggzv z96!wHN%*+VP~rgD(a(e$a`-`GM&j9>dKp_E1 z3&O15{L+an&xlun@dLzTj3V3w4bODlMsD}hM{cW5_j@%Vm?I@$u{W~#gV!>G3-7{Y znnpiTfSBeipGzwa*ylm?4>%Uq#-7D{AaFt04LV@YU*~B%}yFzW+d`pSM zr*V)@%__g9TLh`YiZ~P4?m44wu5ba-aF=ng8AmT<^JG9nTZjw&TJ#Sa)f*$Nm#AX~ zk3U-_B_gF1rIo(QpZzU=P^Y>S{x8@Z8lFPnaJ!>$044t?%~>6kN{3v5k-J_GTM^h% zuvhk3$0)!O(Vkuz-FzSKHVFSmho!?d+ja3cP09^R%aOFTO1>^IMaMr*yC7Nr;`M!g z*`W8Qv58Wo6mq)iqG!5wyYhqRxR%c?Gad=O0VaP!$Jz&i1md()urY6E3hH9mTGC>L z#7awd#r5eXo&l`z(egV*T_dQElaY2UC{v#Wud3cy8aX#2Pf-mTh^D(EFly z-w4UzX^%y&cQnNamr6+J6ZlD2RAdF@4Y!ihcTH*^SQRu~u(^ZK7S3 zf8{u4>CBa}hY?ZECOIu8&Nbyu8->3o|Aeyw_SZs1Gqd?M^)7JGXE8l&Mn|A$SizfTl4@}_kr=YC6QpSJ=q&cW6A0%nQNcQDVPtd@9m@O~C9tSv3zG zJ-+>s_2F+()r8SDCPMFk9#=!f__dleN}ALUQTwat#7_?&tA(BUwT5JRVfG%W5bgmD zWr8Gx>@u6&S7$?`u1%eYvSI&gV@?A+*x~4nVG6!0_V-Nk$&htGI zG(1gZ=igR8**U&$hU2wP?)5I>etVhjIc0--avd`WZ4{1o#`%A57e8A2sHM8NHzI`y z@%SSV(xGDaOLlycD&>oL`M5BT- zA&pgng(&m?euXIO!TP>4vmSC5g(>qLApG0n!Yj^DU)Qd*SqmMss`iO$LG zYCXYL+a61*&ujkN2hAPC{RK=Tz3dCcTL)fg57msd(O^DO6r_50?!=We>84|^ zjr{asf3Asd*l6?c`93Lrv|^231_2Fe0i~M*(XRtBy;@X2>OFcKdAfM8zE;QnzU0Vv zpIN6JE*<~l=Gn-12IDVa-kX1Lje(eXUyRV9qo>MW1!_>MHK~m~L#jOqtr_ZY@%lky z?Sek)N@r*DbxhLZsutwI%5fOqS}x}JA(!CV++CQ0+cqMDeenFyFS_vqO?#8*b}$3T z?Nro)VtpDL9gIPy3@K_qv!BtbzxrbGBwOhJ>H`lD{*#cLJCyC3i~F%#7{bijwAvVR F>wniWGa3K@ literal 0 HcmV?d00001 From dffcee52d7ae0723f35bc535f33fe9c11052a069 Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Tue, 13 Jun 2023 11:52:40 +0200 Subject: [PATCH 02/11] feat: cloudflare integration auth page --- frontend/src/pages/integrations/[id].tsx | 5 ++ .../cloudflare-pages/authorize.tsx | 88 +++++++++++++++++++ .../integrations/cloudflare-pages/create.tsx | 0 3 files changed, 93 insertions(+) create mode 100644 frontend/src/pages/integrations/cloudflare-pages/authorize.tsx create mode 100644 frontend/src/pages/integrations/cloudflare-pages/create.tsx diff --git a/frontend/src/pages/integrations/[id].tsx b/frontend/src/pages/integrations/[id].tsx index 435c904f5..48f34532b 100644 --- a/frontend/src/pages/integrations/[id].tsx +++ b/frontend/src/pages/integrations/[id].tsx @@ -219,6 +219,9 @@ export default function Integrations() { case 'hashicorp-vault': link = `${window.location.origin}/integrations/hashicorp-vault/authorize`; break; + case 'cloudflare-pages': + link = `${window.location.origin}/integrations/cloudflare-pages/authorize`; + break; default: break; } @@ -283,6 +286,8 @@ export default function Integrations() { case 'hashicorp-vault': link = `${window.location.origin}/integrations/hashicorp-vault/create?integrationAuthId=${integrationAuth._id}`; break; + case 'cloudflare-pages': + link = `${window.location.origin}/integrations/cloudflare-pages/create?integrationAutHId=${integrationAuth._id}`; default: break; } diff --git a/frontend/src/pages/integrations/cloudflare-pages/authorize.tsx b/frontend/src/pages/integrations/cloudflare-pages/authorize.tsx new file mode 100644 index 000000000..71b6059e8 --- /dev/null +++ b/frontend/src/pages/integrations/cloudflare-pages/authorize.tsx @@ -0,0 +1,88 @@ +import { useState } from 'react'; +import { useRouter } from 'next/router'; +import { Card, CardTitle, FormControl, Input, Button } from '../../../components/v2'; +import saveIntegrationAccessToken from '~/pages/api/integrations/saveIntegrationAccessToken'; + +export default function CloudflarePagesIntegrationPage() { + const router = useRouter(); + const [accessKey, setAccessKey] = useState(''); + const [accessKeyErrorText, setAccessKeyErrorText] = useState(''); + const [accountId, setAccountId] = useState(''); + const [accountIdErrorText, setAccountIdErrorText] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const handleButtonClick = async () => { + try { + setAccessKeyErrorText(''); + setAccountIdErrorText(''); + if (accessKey.length === 0 || accountId.length === 0) { + if (accessKey.length === 0) setAccessKeyErrorText('Access key cannot be blank!'); + if (accountId.length === 0) setAccountIdErrorText('Account ID cannot be blank!'); + return; + } + + setIsLoading(true); + + const integrationAuth = await saveIntegrationAccessToken({ + workspaceId: localStorage.getItem('projectData.id'), + integration: 'cloudflare-pages', + accessId: accessKey, + accessToken: accountId, + url: null, + namespace: null + }); + + setAccessKey(''); + setAccountId(''); + setIsLoading(false); + + router.push(`/integrations/cloudflare-pages/create?integrationAuthId=${integrationAuth._id}`); + } catch (err) { + console.error(err); + } + } + + return ( +
+ + Cloudflare Pages Integration + + setAccessKey(e.target.value)} + /> + + + setAccountId(e.target.value)} + /> + + + +
+ ); +} + +CloudflarePagesIntegrationPage.requireAuth = true; \ No newline at end of file diff --git a/frontend/src/pages/integrations/cloudflare-pages/create.tsx b/frontend/src/pages/integrations/cloudflare-pages/create.tsx new file mode 100644 index 000000000..e69de29bb From c64cf39b6990764edfb42b03471a4a7f1b3892d9 Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Tue, 13 Jun 2023 12:37:07 +0200 Subject: [PATCH 03/11] feat: cloudflare pages integration create page --- .../integrations/cloudflare-pages/create.tsx | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/frontend/src/pages/integrations/cloudflare-pages/create.tsx b/frontend/src/pages/integrations/cloudflare-pages/create.tsx index e69de29bb..19d4e39b1 100644 --- a/frontend/src/pages/integrations/cloudflare-pages/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-pages/create.tsx @@ -0,0 +1,135 @@ +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/router'; +import { useGetWorkspaceById } from '~/hooks/api'; +import queryString from 'query-string'; +import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from '~/hooks/api/integrationAuth'; +import createIntegration from '~/pages/api/integrations/createIntegration'; +import { Button, Card, CardTitle, FormControl, Select, SelectItem } from '~/components/v2'; + +export default function CloudflarePagesIntegrationPage() { + const router = useRouter(); + + const { integrationAuthId } = queryString.parse(router.asPath.split('?')[1]); + const { data: workspace } = useGetWorkspaceById(localStorage.getItem('projectData.id') ?? ''); + const { data: integrationAuth } = useGetIntegrationAuthById((integrationAuthId as string) ?? ''); + const { data: integrationAuthApps } = useGetIntegrationAuthApps({ + integrationAuthId: (integrationAuthId as string) ?? '' + }); + + const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(''); + const [targetApp, setTargetApp] = useState(''); + const [targetAppId, setTargetAppId] = useState(''); + + const [isLoading, setIsLoading] = useState(false); + + useEffect(() => { + if (workspace) { + setSelectedSourceEnvironment(workspace.environments[0].slug); + } + }, [workspace]); + + useEffect(() => { + if (integrationAuthApps) { + if (integrationAuthApps.length > 0) { + setTargetApp(integrationAuthApps[0].name); + setTargetAppId(String(integrationAuthApps[0].appId)); + } else { + setTargetApp('none'); + } + } + }, [integrationAuthApps]); + + const handleButtonClick = async () => { + try { + if (!integrationAuth?._id) return; + + setIsLoading(true); + + await createIntegration({ + integrationAuthId: integrationAuth?._id, + isActive: true, + app: targetApp, + appId: targetAppId, + sourceEnvironment: selectedSourceEnvironment, + targetEnvironment: null, + targetEnvironmentId: null, + targetService: null, + targetServiceId: null, + owner: null, + path: null, + region: null + }); + + setIsLoading(false); + + router.push(`/integrations/${localStorage.getItem('projectData.id')}`); + } catch(err) { + console.error(err); + } + } + + return integrationAuth && + workspace && + selectedSourceEnvironment && + integrationAuthApps && + targetApp ? ( +
+ + Cloudflare Pages Integration + + + + + + + + +
+ ) : ( +
+ ); +} + +CloudflarePagesIntegrationPage.requireAuth = true; \ No newline at end of file From 381806d84b53eec1e9d18efef33058a58ab187c8 Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Thu, 15 Jun 2023 09:46:09 +0200 Subject: [PATCH 04/11] feat: initial getApps for Cloudflare Pages --- backend/src/integrations/apps.ts | 44 ++++++++++++++++++- backend/src/models/integrationAuth.ts | 6 ++- backend/src/variables/integration.ts | 5 ++- frontend/public/data/frequentConstants.ts | 3 +- .../cloudflare-pages/authorize.tsx | 4 +- 5 files changed, 54 insertions(+), 8 deletions(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index f8ed77277..e7f745974 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -17,6 +17,7 @@ import { INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, INTEGRATION_CHECKLY, + INTEGRATION_CLOUDFLARE_PAGES, INTEGRATION_HEROKU_API_URL, INTEGRATION_GITLAB_API_URL, INTEGRATION_VERCEL_API_URL, @@ -27,7 +28,8 @@ import { INTEGRATION_CIRCLECI_API_URL, INTEGRATION_TRAVISCI_API_URL, INTEGRATION_SUPABASE_API_URL, - INTEGRATION_CHECKLY_API_URL + INTEGRATION_CHECKLY_API_URL, + INTEGRATION_CLOUDFLARE_PAGES_API_URL } from "../variables"; interface App { @@ -127,6 +129,12 @@ const getApps = async ({ accessToken, }); break; + case INTEGRATION_CLOUDFLARE_PAGES: + apps = await getAppsCloudflarePages({ + accessToken, + accountId: '' + }) + break; } return apps; @@ -636,4 +644,38 @@ const getAppsCheckly = async ({ accessToken }: { accessToken: string }) => { return apps; }; +/** + * Return list of projects for the Cloudflare Pages integration + * @param {Object} obj + * @param {String} obj.accessToken - api key for the Cloudflare API + * @returns {Object[]} apps - Cloudflare Pages projects + * @returns {String} apps.name - name of Cloudflare Pages project + */ +const getAppsCloudflarePages = async ({ + accessToken, + accountId +}: { + accessToken: string; + accountId?: string; +}) => { + const { data } = await standardRequest.get( + `${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accountId}/pages/projects`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept": "application/json", + }, + } + ); + + const apps = data.map((a: any) => { + return { + name: a["project_name"], + appId: a.id, + }; + }); + + return apps; +} + export { getApps }; diff --git a/backend/src/models/integrationAuth.ts b/backend/src/models/integrationAuth.ts index cc28c9fd6..9e2cb0c19 100644 --- a/backend/src/models/integrationAuth.ts +++ b/backend/src/models/integrationAuth.ts @@ -15,6 +15,7 @@ import { INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, INTEGRATION_HASHICORP_VAULT, + INTEGRATION_CLOUDFLARE_PAGES, ALGORITHM_AES_256_GCM, ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64 @@ -23,7 +24,7 @@ import { export interface IIntegrationAuth extends Document { _id: Types.ObjectId; workspace: Types.ObjectId; - integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly'; + integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'gitlab' | 'render' | 'railway' | 'flyio' | 'azure-key-vault' | 'circleci' | 'travisci' | 'supabase' | 'aws-parameter-store' | 'aws-secret-manager' | 'checkly' | 'cloudflare-pages'; teamId: string; accountId: string; url: string; @@ -66,7 +67,8 @@ const integrationAuthSchema = new Schema( INTEGRATION_CIRCLECI, INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, - INTEGRATION_HASHICORP_VAULT + INTEGRATION_HASHICORP_VAULT, + INTEGRATION_CLOUDFLARE_PAGES, ], required: true, }, diff --git a/backend/src/variables/integration.ts b/backend/src/variables/integration.ts index 8935e7b49..06bd74fdf 100644 --- a/backend/src/variables/integration.ts +++ b/backend/src/variables/integration.ts @@ -23,8 +23,8 @@ export const INTEGRATION_CIRCLECI = "circleci"; export const INTEGRATION_TRAVISCI = "travisci"; export const INTEGRATION_SUPABASE = 'supabase'; export const INTEGRATION_CHECKLY = 'checkly'; -export const INTEGRATION_CLOUDFLARE_PAGES = 'cloudflare-pages'; export const INTEGRATION_HASHICORP_VAULT = 'hashicorp-vault'; +export const INTEGRATION_CLOUDFLARE_PAGES = 'cloudflare-pages'; export const INTEGRATION_SET = new Set([ INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_HEROKU, @@ -38,7 +38,8 @@ export const INTEGRATION_SET = new Set([ INTEGRATION_TRAVISCI, INTEGRATION_SUPABASE, INTEGRATION_CHECKLY, - INTEGRATION_HASHICORP_VAULT + INTEGRATION_HASHICORP_VAULT, + INTEGRATION_CLOUDFLARE_PAGES ]); // integration types diff --git a/frontend/public/data/frequentConstants.ts b/frontend/public/data/frequentConstants.ts index e873f1a72..480061e1a 100644 --- a/frontend/public/data/frequentConstants.ts +++ b/frontend/public/data/frequentConstants.ts @@ -18,7 +18,8 @@ const integrationSlugNameMapping: Mapping = { 'travisci': 'TravisCI', 'supabase': 'Supabase', 'checkly': 'Checkly', - 'hashicorp-vault': 'Vault' + 'hashicorp-vault': 'Vault', + 'cloudflare-pages': 'Cloudflare Pages' } const envMapping: Mapping = { diff --git a/frontend/src/pages/integrations/cloudflare-pages/authorize.tsx b/frontend/src/pages/integrations/cloudflare-pages/authorize.tsx index 71b6059e8..cbd20f018 100644 --- a/frontend/src/pages/integrations/cloudflare-pages/authorize.tsx +++ b/frontend/src/pages/integrations/cloudflare-pages/authorize.tsx @@ -26,8 +26,8 @@ export default function CloudflarePagesIntegrationPage() { const integrationAuth = await saveIntegrationAccessToken({ workspaceId: localStorage.getItem('projectData.id'), integration: 'cloudflare-pages', - accessId: accessKey, - accessToken: accountId, + accessId: accountId, + accessToken: accessKey, url: null, namespace: null }); From 1a54bf34ef6db8efe43b011cf1d6ae9d57ff0905 Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Mon, 19 Jun 2023 13:58:38 +0200 Subject: [PATCH 05/11] feat: fix getApps and create for cloudflare pages integration --- backend/src/integrations/apps.ts | 7 ++-- .../integrations/cloudflare-pages/create.tsx | 41 ++++++++++--------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/backend/src/integrations/apps.ts b/backend/src/integrations/apps.ts index e359ffa2b..245fd931d 100644 --- a/backend/src/integrations/apps.ts +++ b/backend/src/integrations/apps.ts @@ -134,7 +134,7 @@ const getApps = async ({ case INTEGRATION_CLOUDFLARE_PAGES: apps = await getAppsCloudflarePages({ accessToken, - accountId: '' + accountId: accessId }) break; } @@ -670,13 +670,12 @@ const getAppsCloudflarePages = async ({ } ); - const apps = data.map((a: any) => { + const apps = data.result.map((a: any) => { return { - name: a["project_name"], + name: a.name, appId: a.id, }; }); - return apps; } diff --git a/frontend/src/pages/integrations/cloudflare-pages/create.tsx b/frontend/src/pages/integrations/cloudflare-pages/create.tsx index 19d4e39b1..d3ba19e37 100644 --- a/frontend/src/pages/integrations/cloudflare-pages/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-pages/create.tsx @@ -57,7 +57,8 @@ export default function CloudflarePagesIntegrationPage() { targetServiceId: null, owner: null, path: null, - region: null + region: null, + secretPath: '/', }); setIsLoading(false); @@ -82,14 +83,14 @@ export default function CloudflarePagesIntegrationPage() { onValueChange={(val) => setSelectedSourceEnvironment(val)} className="w-full border border-mineshaft-500" > - {workspace?.environments.map((sourceEnvironment) => { - - {sourceEnvironment.name} - - })} + {workspace?.environments.map((sourceEnvironment) => ( + + {sourceEnvironment.name} + + ))} @@ -100,18 +101,18 @@ export default function CloudflarePagesIntegrationPage() { isDisabled={integrationAuthApps.length === 0} > {integrationAuthApps.length > 0 ? ( - integrationAuthApps.map((integrationAuthApp) => { - - {integrationAuthApp.name} - - }) - ) : ( - - No projects found + integrationAuthApps.map((integrationAuthApp) => ( + + {integrationAuthApp.name} + )) + ) : ( + + No apps found + )} From 17097965d95801ee43e0ee2366300b979d178351 Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Mon, 19 Jun 2023 15:14:57 +0200 Subject: [PATCH 06/11] feat: cloudflare pages integration sync --- backend/src/integrations/sync.ts | 72 ++++++++++++++++++++++++++++++- backend/src/models/integration.ts | 5 ++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 76d009944..7d003b4f5 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -37,7 +37,9 @@ import { INTEGRATION_SUPABASE_API_URL, INTEGRATION_CHECKLY, INTEGRATION_CHECKLY_API_URL, - INTEGRATION_HASHICORP_VAULT + INTEGRATION_HASHICORP_VAULT, + INTEGRATION_CLOUDFLARE_PAGES, + INTEGRATION_CLOUDFLARE_PAGES_API_URL } from "../variables"; import { standardRequest} from '../config/request'; @@ -210,6 +212,14 @@ const syncSecrets = async ({ accessToken }); break; + case INTEGRATION_CLOUDFLARE_PAGES: + await syncSecretsCloudflarePages({ + integration, + secrets, + accessId, + accessToken + }); + break; } }; @@ -1833,4 +1843,64 @@ const syncSecretsHashiCorpVault = async ({ ); }; +const syncSecretsCloudflarePages = async ({ + integration, + secrets, + accessId, + accessToken, +}: { + integration: IIntegration; + secrets: any; + accessId: string | null; + accessToken: string; +}) => { + // get secrets from cloudflare pages + const getSecretsRes = ( + await standardRequest.get( + `${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}`, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept": "application/json", + }, + } + ) + ) + .data.result['deployment_configs'].production['env_vars']; + + // copy the secrets object, so we can set deleted keys to null + const secretsObj: any = {...secrets}; + + for (const [key, val] of Object.entries(secretsObj)) { + secretsObj[key] = { value: val }; + } + + for await (const key of Object.keys(getSecretsRes)) { + if (!(key in secrets)) { + // case: secret deos not exist in infisical + // -> delete secret from cloudflare pages + secretsObj[key] = null; + } + } + + await standardRequest.patch( + `${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}`, + { + data: { + "deployment_configs": { + "production": { + "env_vars": secretsObj + } + } + } + }, + { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept": "application/json", + }, + } + ) +} + export { syncSecrets }; diff --git a/backend/src/models/integration.ts b/backend/src/models/integration.ts index c23cf69f1..678cdcc56 100644 --- a/backend/src/models/integration.ts +++ b/backend/src/models/integration.ts @@ -16,6 +16,7 @@ import { INTEGRATION_SUPABASE, INTEGRATION_CHECKLY, INTEGRATION_HASHICORP_VAULT, + INTEGRATION_CLOUDFLARE_PAGES } from "../variables"; export interface IIntegration { @@ -50,7 +51,8 @@ export interface IIntegration { | "travisci" | "supabase" | "checkly" - | "hashicorp-vault"; + | "hashicorp-vault" + | "cloudflare-pages"; integrationAuth: Types.ObjectId; } @@ -138,6 +140,7 @@ const integrationSchema = new Schema( INTEGRATION_SUPABASE, INTEGRATION_CHECKLY, INTEGRATION_HASHICORP_VAULT, + INTEGRATION_CLOUDFLARE_PAGES, ], required: true, }, From 59737f89c101a133f2629adddb7fd67d1493b27c Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Mon, 19 Jun 2023 15:44:41 +0200 Subject: [PATCH 07/11] fix: cloudlfare pages sync request fix --- backend/src/integrations/sync.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 7d003b4f5..81b445308 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1883,14 +1883,14 @@ const syncSecretsCloudflarePages = async ({ } } - await standardRequest.patch( + console.log(secretsObj); // -> { test: { value: 'test' }, aaa: null, test_cf: null } + + const result = await standardRequest.patch( `${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}`, { - data: { - "deployment_configs": { - "production": { - "env_vars": secretsObj - } + "deployment_configs": { + "production": { + "env_vars": secretsObj } } }, @@ -1901,6 +1901,8 @@ const syncSecretsCloudflarePages = async ({ }, } ) + + console.log(result); } export { syncSecrets }; From 1760b319d36ab4db79c525e387f1f95d27b018a3 Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Mon, 19 Jun 2023 16:00:53 +0200 Subject: [PATCH 08/11] cleanup --- backend/src/integrations/sync.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 81b445308..6dfe04930 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1872,19 +1872,19 @@ const syncSecretsCloudflarePages = async ({ const secretsObj: any = {...secrets}; for (const [key, val] of Object.entries(secretsObj)) { - secretsObj[key] = { value: val }; + secretsObj[key] = { type: "secret_text", value: val }; } - for await (const key of Object.keys(getSecretsRes)) { - if (!(key in secrets)) { - // case: secret deos not exist in infisical - // -> delete secret from cloudflare pages - secretsObj[key] = null; + if (getSecretsRes) { // if there are no env vars set in cloudflare pages, none have to be deleted either + for await (const key of Object.keys(getSecretsRes)) { + if (!(key in secrets)) { + // case: secret deos not exist in infisical + // -> delete secret from cloudflare pages + secretsObj[key] = null; + } } } - console.log(secretsObj); // -> { test: { value: 'test' }, aaa: null, test_cf: null } - const result = await standardRequest.patch( `${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}`, { @@ -1901,8 +1901,6 @@ const syncSecretsCloudflarePages = async ({ }, } ) - - console.log(result); } export { syncSecrets }; From e6ad153e83d1a75ce434d4338789d75d54275082 Mon Sep 17 00:00:00 2001 From: Stijn-Kuijper Date: Tue, 20 Jun 2023 13:43:22 +0200 Subject: [PATCH 09/11] feat: option to choose target environment --- backend/src/integrations/sync.ts | 30 +++++++++++++------ .../integrations/cloudflare-pages/create.tsx | 27 ++++++++++++++++- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index 6dfe04930..729097213 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -1854,6 +1854,8 @@ const syncSecretsCloudflarePages = async ({ accessId: string | null; accessToken: string; }) => { + const targetEnvironment = integration.targetEnvironment; + // get secrets from cloudflare pages const getSecretsRes = ( await standardRequest.get( @@ -1866,7 +1868,7 @@ const syncSecretsCloudflarePages = async ({ } ) ) - .data.result['deployment_configs'].production['env_vars']; + .data.result['deployment_configs'][targetEnvironment]['env_vars']; // copy the secrets object, so we can set deleted keys to null const secretsObj: any = {...secrets}; @@ -1878,22 +1880,32 @@ const syncSecretsCloudflarePages = async ({ if (getSecretsRes) { // if there are no env vars set in cloudflare pages, none have to be deleted either for await (const key of Object.keys(getSecretsRes)) { if (!(key in secrets)) { - // case: secret deos not exist in infisical + // case: secret does not exist in infisical // -> delete secret from cloudflare pages secretsObj[key] = null; } } } + const data = targetEnvironment === 'production' + ? { + "deployment_configs": { + "production" : { + "env_vars": secretsObj + } + } + } + : { + "deployment_configs": { + "preview": { + "env_vars": secretsObj + } + } + } + const result = await standardRequest.patch( `${INTEGRATION_CLOUDFLARE_PAGES_API_URL}/client/v4/accounts/${accessId}/pages/projects/${integration.app}`, - { - "deployment_configs": { - "production": { - "env_vars": secretsObj - } - } - }, + data, { headers: { Authorization: `Bearer ${accessToken}`, diff --git a/frontend/src/pages/integrations/cloudflare-pages/create.tsx b/frontend/src/pages/integrations/cloudflare-pages/create.tsx index d3ba19e37..d4cbd5545 100644 --- a/frontend/src/pages/integrations/cloudflare-pages/create.tsx +++ b/frontend/src/pages/integrations/cloudflare-pages/create.tsx @@ -6,6 +6,11 @@ import { useGetIntegrationAuthApps, useGetIntegrationAuthById } from '~/hooks/ap import createIntegration from '~/pages/api/integrations/createIntegration'; import { Button, Card, CardTitle, FormControl, Select, SelectItem } from '~/components/v2'; +const cloudflareEnvironments = [ + { name: 'Production', slug: 'production' }, + { name: 'Preview', slug: 'preview' } +]; + export default function CloudflarePagesIntegrationPage() { const router = useRouter(); @@ -19,6 +24,7 @@ export default function CloudflarePagesIntegrationPage() { const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(''); const [targetApp, setTargetApp] = useState(''); const [targetAppId, setTargetAppId] = useState(''); + const [targetEnvironment, setTargetEnvironment] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -33,8 +39,10 @@ export default function CloudflarePagesIntegrationPage() { if (integrationAuthApps.length > 0) { setTargetApp(integrationAuthApps[0].name); setTargetAppId(String(integrationAuthApps[0].appId)); + setTargetEnvironment(cloudflareEnvironments[0].slug); } else { setTargetApp('none'); + setTargetEnvironment(cloudflareEnvironments[0].slug); } } }, [integrationAuthApps]); @@ -51,7 +59,7 @@ export default function CloudflarePagesIntegrationPage() { app: targetApp, appId: targetAppId, sourceEnvironment: selectedSourceEnvironment, - targetEnvironment: null, + targetEnvironment, targetEnvironmentId: null, targetService: null, targetServiceId: null, @@ -73,6 +81,7 @@ export default function CloudflarePagesIntegrationPage() { workspace && selectedSourceEnvironment && integrationAuthApps && + targetEnvironment && targetApp ? (
@@ -116,6 +125,22 @@ export default function CloudflarePagesIntegrationPage() { )} + + +