mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #3268 from Infisical/feat/addCustomDomainToWindmillIntegration
Add Windmill custom api url domain
This commit is contained in:
@@ -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 | null }) => {
|
||||
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<object>(
|
||||
`${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<object>(
|
||||
`${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:
|
||||
|
||||
@@ -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<WindmillSecret[]>(`${IntegrationUrls.WINDMILL_API_URL}/w/${integration.appId}/variables/list`, {
|
||||
await request.get<WindmillSecret[]>(`${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"
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -192,6 +192,15 @@ export const IntegrationConnectionSection = ({ integration }: Props) => {
|
||||
);
|
||||
}
|
||||
|
||||
if (integration.integration === "windmill" && integration.url) {
|
||||
return (
|
||||
<div>
|
||||
<FormLabel className="text-sm font-semibold text-mineshaft-300" label="Instance URL" />
|
||||
<div className="text-sm text-mineshaft-300">{integration.url}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,31 +3,75 @@ import { useNavigate } from "@tanstack/react-router";
|
||||
|
||||
import { Button, Card, CardTitle, FormControl, Input } from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
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<string | null>(null);
|
||||
const [apiUrlErrorText, setApiUrlErrorText] = useState("");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const isLocalOrPrivateIpAddress = (url: string): boolean => {
|
||||
try {
|
||||
const validUrl = new URL(url);
|
||||
// Check for localhost
|
||||
if (validUrl.hostname === "localhost" || validUrl.hostname === "127.0.0.1") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for 10.x.x.x
|
||||
if (validUrl.hostname.match(/^10\.\d+\.\d+\.\d+/)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for host.docker.internal
|
||||
if (validUrl.hostname === "host.docker.internal") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for 192.168.x.x
|
||||
if (validUrl.hostname.match(/^192\.168\.\d+\.\d+/)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonClick = async () => {
|
||||
try {
|
||||
setApiKeyErrorText("");
|
||||
setApiUrlErrorText("");
|
||||
if (apiKey.length === 0) {
|
||||
setApiKeyErrorText("API Key cannot be blank");
|
||||
return;
|
||||
}
|
||||
|
||||
if (apiUrl) {
|
||||
if (!apiUrl.startsWith("http://") && !apiUrl.startsWith("https://")) {
|
||||
setApiUrlErrorText("API URL must start with http:// or https://");
|
||||
return;
|
||||
}
|
||||
if (isInfisicalCloud() && isLocalOrPrivateIpAddress(apiUrl)) {
|
||||
setApiUrlErrorText("Local IPs not allowed as URL");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const integrationAuth = await mutateAsync({
|
||||
workspaceId: currentWorkspace.id,
|
||||
integration: "windmill",
|
||||
accessToken: apiKey
|
||||
accessToken: apiKey,
|
||||
url: apiUrl ?? undefined
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
@@ -57,6 +101,18 @@ export const WindmillAuthorizePage = () => {
|
||||
>
|
||||
<Input placeholder="" value={apiKey} onChange={(e) => setApiKey(e.target.value)} />
|
||||
</FormControl>
|
||||
<FormControl
|
||||
label="Windmill Instance URL"
|
||||
errorText={apiUrlErrorText}
|
||||
isError={apiUrlErrorText !== ""}
|
||||
tooltipText="If you are using a custom domain, enter it here. Otherwise, leave it blank."
|
||||
>
|
||||
<Input
|
||||
value={apiUrl ?? ""}
|
||||
onChange={(e) => setApiUrl(e.target.value.trim() === "" ? null : e.target.value.trim())}
|
||||
placeholder="https://xxxx.windmill.dev"
|
||||
/>
|
||||
</FormControl>
|
||||
<Button
|
||||
onClick={handleButtonClick}
|
||||
color="mineshaft"
|
||||
|
||||
@@ -67,7 +67,8 @@ export const WindmillConfigurePage = () => {
|
||||
(integrationAuthApp) => integrationAuthApp.name === targetApp
|
||||
)?.appId,
|
||||
sourceEnvironment: selectedSourceEnvironment,
|
||||
secretPath
|
||||
secretPath,
|
||||
url: integrationAuth.url ?? undefined
|
||||
});
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
Reference in New Issue
Block a user