From dcd3b5df56306f2a73a379d695b9255f2af6ec2d Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Tue, 18 Mar 2025 19:01:52 -0300 Subject: [PATCH 1/4] Add Windmill custom api url domain --- .../integration-auth/integration-app-list.ts | 27 +++++++++---------- .../integration-sync-secret.ts | 24 +++++++---------- .../WindmillAuthorizePage.tsx | 24 +++++++++++++++-- .../WindmillConfigurePage.tsx | 3 ++- 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/backend/src/services/integration-auth/integration-app-list.ts b/backend/src/services/integration-auth/integration-app-list.ts index 8b48e20d4..abfa7e563 100644 --- a/backend/src/services/integration-auth/integration-app-list.ts +++ b/backend/src/services/integration-auth/integration-app-list.ts @@ -923,16 +923,14 @@ const getAppsCodefresh = async ({ accessToken }: { accessToken: string }) => { /** * Return list of projects for Windmill integration */ -const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { - const { data } = await request.get<{ id: string; name: string }[]>( - `${IntegrationUrls.WINDMILL_API_URL}/workspaces/list`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - "Accept-Encoding": "application/json" - } +const getAppsWindmill = async ({ accessToken, url }: { accessToken: string; url?: string }) => { + const apiUrl = url ? `${url}/api` : IntegrationUrls.WINDMILL_API_URL; + const { data } = await request.get<{ id: string; name: string }[]>(`${apiUrl}/workspaces/list`, { + headers: { + Authorization: `Bearer ${accessToken}`, + "Accept-Encoding": "application/json" } - ); + }); // check for write access of secrets in windmill workspaces const writeAccessCheck = data.map(async (app) => { @@ -941,7 +939,7 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { const folderPath = "f/folder/variable"; const { data: writeUser } = await request.post( - `${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/create`, + `${apiUrl}/w/${app.id}/variables/create`, { path: userPath, value: "variable", @@ -957,7 +955,7 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { ); const { data: writeFolder } = await request.post( - `${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/create`, + `${apiUrl}/w/${app.id}/variables/create`, { path: folderPath, value: "variable", @@ -974,14 +972,14 @@ const getAppsWindmill = async ({ accessToken }: { accessToken: string }) => { // is write access is allowed then delete the created secrets from workspace if (writeUser && writeFolder) { - await request.delete(`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${userPath}`, { + await request.delete(`${apiUrl}/w/${app.id}/variables/delete/${userPath}`, { headers: { Authorization: `Bearer ${accessToken}`, "Accept-Encoding": "application/json" } }); - await request.delete(`${IntegrationUrls.WINDMILL_API_URL}/w/${app.id}/variables/delete/${folderPath}`, { + await request.delete(`${apiUrl}/w/${app.id}/variables/delete/${folderPath}`, { headers: { Authorization: `Bearer ${accessToken}`, "Accept-Encoding": "application/json" @@ -1316,7 +1314,8 @@ export const getApps = async ({ case Integrations.WINDMILL: return getAppsWindmill({ - accessToken + accessToken, + url }); case Integrations.DIGITAL_OCEAN_APP_PLATFORM: diff --git a/backend/src/services/integration-auth/integration-sync-secret.ts b/backend/src/services/integration-auth/integration-sync-secret.ts index 93318762b..56e506759 100644 --- a/backend/src/services/integration-auth/integration-sync-secret.ts +++ b/backend/src/services/integration-auth/integration-sync-secret.ts @@ -4127,10 +4127,10 @@ const syncSecretsWindmill = async ({ is_secret: boolean; description?: string; } - + const apiUrl = integration.url ? `${integration.url}/api` : IntegrationUrls.WINDMILL_API_URL; // get secrets stored in windmill workspace const res = ( - await request.get(`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/list`, { + await request.get(`${apiUrl}/w/${integration.appId}/variables/list`, { headers: { Authorization: `Bearer ${accessToken}`, "Accept-Encoding": "application/json" @@ -4146,7 +4146,6 @@ const syncSecretsWindmill = async ({ // eslint-disable-next-line const pattern = new RegExp("^(u/|f/)[a-zA-Z0-9_-]+/([a-zA-Z0-9_-]+/)*[a-zA-Z0-9_-]*[^/]$"); - for await (const key of Object.keys(secrets)) { if ((key.startsWith("u/") || key.startsWith("f/")) && pattern.test(key)) { if (!(key in res)) { @@ -4154,7 +4153,7 @@ const syncSecretsWindmill = async ({ // -> create secret await request.post( - `${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/create`, + `${apiUrl}/w/${integration.appId}/variables/create`, { path: key, value: secrets[key].value, @@ -4171,7 +4170,7 @@ const syncSecretsWindmill = async ({ } else { // -> update secret await request.post( - `${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/update/${res[key].path}`, + `${apiUrl}/w/${integration.appId}/variables/update/${res[key].path}`, { path: key, value: secrets[key].value, @@ -4192,16 +4191,13 @@ const syncSecretsWindmill = async ({ for await (const key of Object.keys(res)) { if (!(key in secrets)) { // -> delete secret - await request.delete( - `${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/delete/${res[key].path}`, - { - headers: { - Authorization: `Bearer ${accessToken}`, - "Content-Type": "application/json", - "Accept-Encoding": "application/json" - } + await request.delete(`${apiUrl}/w/${integration.appId}/variables/delete/${res[key].path}`, { + headers: { + Authorization: `Bearer ${accessToken}`, + "Content-Type": "application/json", + "Accept-Encoding": "application/json" } - ); + }); } } }; diff --git a/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx b/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx index 0071da9aa..946b5d8e2 100644 --- a/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx +++ b/frontend/src/pages/secret-manager/integrations/WindmillAuthorizePage/WindmillAuthorizePage.tsx @@ -8,26 +8,34 @@ import { useSaveIntegrationAccessToken } from "@app/hooks/api"; export const WindmillAuthorizePage = () => { const navigate = useNavigate(); const { mutateAsync } = useSaveIntegrationAccessToken(); - const { currentWorkspace } = useWorkspace(); const [apiKey, setApiKey] = useState(""); const [apiKeyErrorText, setApiKeyErrorText] = useState(""); + const [apiUrl, setApiUrl] = useState(null); + const [apiUrlErrorText, setApiUrlErrorText] = useState(""); const [isLoading, setIsLoading] = useState(false); const handleButtonClick = async () => { try { setApiKeyErrorText(""); + setApiUrlErrorText(""); if (apiKey.length === 0) { setApiKeyErrorText("API Key cannot be blank"); return; } + if (apiUrl && !apiUrl.startsWith("http://") && !apiUrl.startsWith("https://")) { + setApiUrlErrorText("API URL must start with http:// or https://"); + return; + } + setIsLoading(true); const integrationAuth = await mutateAsync({ workspaceId: currentWorkspace.id, integration: "windmill", - accessToken: apiKey + accessToken: apiKey, + url: apiUrl ?? undefined }); setIsLoading(false); @@ -57,6 +65,18 @@ export const WindmillAuthorizePage = () => { > setApiKey(e.target.value)} /> + + setApiUrl(e.target.value.trim() === "" ? null : e.target.value.trim())} + placeholder="https://xxxx.windmill.dev" + /> +