mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 20:29:34 +00:00
Merge remote-tracking branch 'upstream/main' into feat/39
This commit is contained in:
+42
-42
@@ -1,62 +1,62 @@
|
||||
import "../styles/globals.css";
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
import Layout from "../components/basic/layout";
|
||||
import RouteGuard from "../components/RouteGuard";
|
||||
import { publicPaths } from "../const.js";
|
||||
import { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { initPostHog } from "../components/analytics/posthog";
|
||||
import { ENV } from "../components/utilities/config";
|
||||
import { config } from "@fortawesome/fontawesome-svg-core";
|
||||
|
||||
import { initPostHog } from "~/components/analytics/posthog";
|
||||
import Layout from "~/components/basic/layout";
|
||||
import RouteGuard from "~/components/RouteGuard";
|
||||
import { publicPaths } from "~/const";
|
||||
import { ENV } from "~/utilities/config";
|
||||
|
||||
import "@fortawesome/fontawesome-svg-core/styles.css";
|
||||
import "../styles/globals.css";
|
||||
|
||||
config.autoAddCss = false;
|
||||
|
||||
const App = ({ Component, pageProps, ...appProps }) => {
|
||||
const router = useRouter();
|
||||
const posthog = initPostHog();
|
||||
const router = useRouter();
|
||||
const posthog = initPostHog();
|
||||
|
||||
useEffect(() => {
|
||||
// Init for auto capturing
|
||||
const posthog = initPostHog();
|
||||
useEffect(() => {
|
||||
// Init for auto capturing
|
||||
const posthog = initPostHog();
|
||||
|
||||
const handleRouteChange = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
if (ENV == "production") {
|
||||
posthog.capture("$pageview");
|
||||
}
|
||||
}
|
||||
};
|
||||
const handleRouteChange = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
if (ENV == "production") {
|
||||
posthog.capture("$pageview");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
router.events.on("routeChangeComplete", handleRouteChange);
|
||||
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}, [router.events]);
|
||||
return () => {
|
||||
router.events.off("routeChangeComplete", handleRouteChange);
|
||||
};
|
||||
}, [router.events]);
|
||||
|
||||
// If it's one of these routes, don't add the layout (e.g., these routes are external)
|
||||
if (
|
||||
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
|
||||
!Component.requireAuth
|
||||
) {
|
||||
return (
|
||||
<Component {...pageProps} />
|
||||
)
|
||||
}
|
||||
// If it's one of these routes, don't add the layout (e.g., these routes are external)
|
||||
if (
|
||||
publicPaths.includes("/" + appProps.router.pathname.split("/")[1]) ||
|
||||
!Component.requireAuth
|
||||
) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RouteGuard>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</RouteGuard>
|
||||
);
|
||||
return (
|
||||
<RouteGuard>
|
||||
<Layout>
|
||||
<Component {...pageProps} />
|
||||
</Layout>
|
||||
</RouteGuard>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
{
|
||||
/* <Script
|
||||
/* <Script
|
||||
src="https://www.googletagmanager.com/gtag/js?id=G-DQ1XLJJGG1"
|
||||
strategy="afterInteractive"
|
||||
/>
|
||||
|
||||
@@ -1,33 +1,39 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the second step of the change password process (pake)
|
||||
* @param {*} clientPublicKey
|
||||
* @returns
|
||||
*/
|
||||
const changePassword2 = ({encryptedPrivateKey, iv, tag, salt, verifier, clientProof}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"clientProof": clientProof,
|
||||
"encryptedPrivateKey": encryptedPrivateKey,
|
||||
"iv": iv,
|
||||
"tag": tag,
|
||||
"salt": salt,
|
||||
"verifier": verifier
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to change the password');
|
||||
}
|
||||
})
|
||||
const changePassword2 = ({
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
clientProof,
|
||||
}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/password/change-password", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientProof: clientProof,
|
||||
encryptedPrivateKey: encryptedPrivateKey,
|
||||
iv: iv,
|
||||
tag: tag,
|
||||
salt: salt,
|
||||
verifier: verifier,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to change the password");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default changePassword2;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient.js";
|
||||
import { PATH } from "../../../const.js";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||
|
||||
/**
|
||||
* This function is used to check if the user is authenticated.
|
||||
@@ -9,19 +9,18 @@ import { PATH } from "../../../const.js";
|
||||
* @returns
|
||||
*/
|
||||
const checkAuth = async (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/auth/checkAuth", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Not authorized");
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/auth/checkAuth", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Not authorized");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default checkAuth;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PATH } from "../../../const.js";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This route check the verification code from the email that user just recieved
|
||||
@@ -7,16 +7,16 @@ import { PATH } from "../../../const.js";
|
||||
* @returns
|
||||
*/
|
||||
const checkEmailVerificationCode = (email, code) => {
|
||||
return fetch(PATH + "/api/v1/signup/email/verify", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
code: code,
|
||||
}),
|
||||
});
|
||||
return fetch(PATH + "/api/v1/signup/email/verify", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
code: code,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default checkEmailVerificationCode;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This function is called in the end of the signup process.
|
||||
@@ -16,37 +16,37 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const completeAccountInformationSignup = ({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName,
|
||||
publicKey,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
token
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName,
|
||||
publicKey,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
token,
|
||||
}) => {
|
||||
return fetch(PATH + "/api/v1/signup/complete-account/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
organizationName,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
}),
|
||||
});
|
||||
return fetch(PATH + "/api/v1/signup/complete-account/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
organizationName,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default completeAccountInformationSignup;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This function is called in the end of the signup process.
|
||||
@@ -15,35 +15,35 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const completeAccountInformationSignupInvite = ({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
token
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
token,
|
||||
}) => {
|
||||
return fetch(PATH + "/api/v1/signup/complete-account/invite", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": "Bearer " + token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
publicKey: publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv: iv,
|
||||
tag: tag,
|
||||
salt: salt,
|
||||
verifier: verifier
|
||||
}),
|
||||
});
|
||||
return fetch(PATH + "/api/v1/signup/complete-account/invite", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
publicKey: publicKey,
|
||||
encryptedPrivateKey: ciphertext,
|
||||
iv: iv,
|
||||
tag: tag,
|
||||
salt: salt,
|
||||
verifier: verifier,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default completeAccountInformationSignupInvite;
|
||||
|
||||
@@ -1,32 +1,41 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the route that issues a backup private key that will afterwards be added into a pdf
|
||||
*/
|
||||
const issueBackupPrivateKey = ({encryptedPrivateKey, iv, tag, salt, verifier, clientProof}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/password/backup-private-key", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"clientProof": clientProof,
|
||||
"encryptedPrivateKey": encryptedPrivateKey,
|
||||
"iv": iv,
|
||||
"tag": tag,
|
||||
"salt": salt,
|
||||
"verifier": verifier
|
||||
}),
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
return res;
|
||||
console.log("Failed to issue the backup key");
|
||||
}
|
||||
})
|
||||
const issueBackupPrivateKey = ({
|
||||
encryptedPrivateKey,
|
||||
iv,
|
||||
tag,
|
||||
salt,
|
||||
verifier,
|
||||
clientProof,
|
||||
}) => {
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/password/backup-private-key",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientProof: clientProof,
|
||||
encryptedPrivateKey: encryptedPrivateKey,
|
||||
iv: iv,
|
||||
tag: tag,
|
||||
salt: salt,
|
||||
verifier: verifier,
|
||||
}),
|
||||
}
|
||||
).then((res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
return res;
|
||||
console.log("Failed to issue the backup key");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default issueBackupPrivateKey;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PATH } from "../../../const.js";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This is the first step of the login process (pake)
|
||||
@@ -7,16 +7,16 @@ import { PATH } from "../../../const.js";
|
||||
* @returns
|
||||
*/
|
||||
const login1 = (email, clientPublicKey) => {
|
||||
return fetch(PATH + "/api/v1/auth/login1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientPublicKey,
|
||||
}),
|
||||
});
|
||||
return fetch(PATH + "/api/v1/auth/login1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientPublicKey,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default login1;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This is the second step of the login process
|
||||
@@ -7,25 +7,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const login2 = (email, clientProof) => {
|
||||
return fetch(PATH + "/api/v1/auth/login2", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientProof,
|
||||
}),
|
||||
credentials: "include"
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status == 200) {
|
||||
console.log("User logged in", res);
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to log in");
|
||||
}
|
||||
})
|
||||
return fetch(PATH + "/api/v1/auth/login2", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
clientProof,
|
||||
}),
|
||||
credentials: "include",
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
console.log("User logged in", res);
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to log in");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default login2;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route logs the user out. Note: the user should authorized to do this.
|
||||
@@ -9,28 +9,27 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const logout = async (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/auth/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include"
|
||||
})
|
||||
.then(res => {
|
||||
if (res.status == 200) {
|
||||
SecurityClient.setToken("");
|
||||
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
||||
localStorage.setItem("publicKey", "");
|
||||
localStorage.setItem("encryptedPrivateKey", "");
|
||||
localStorage.setItem("iv", "");
|
||||
localStorage.setItem("tag", "");
|
||||
localStorage.setItem("PRIVATE_KEY", "");
|
||||
console.log("User logged out", res);
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to log out");
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/auth/logout", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
}).then((res) => {
|
||||
if (res.status == 200) {
|
||||
SecurityClient.setToken("");
|
||||
// Delete the cookie by not setting a value; Alternatively clear the local storage
|
||||
localStorage.setItem("publicKey", "");
|
||||
localStorage.setItem("encryptedPrivateKey", "");
|
||||
localStorage.setItem("iv", "");
|
||||
localStorage.setItem("tag", "");
|
||||
localStorage.setItem("PRIVATE_KEY", "");
|
||||
console.log("User logged out", res);
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to log out");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default logout;
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the first step of the change password process (pake)
|
||||
* @param {*} clientPublicKey
|
||||
* @returns
|
||||
*/
|
||||
const SRP1 = ({clientPublicKey}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientPublicKey,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json());
|
||||
} else {
|
||||
console.log('Failed to do the first step of SRP');
|
||||
}
|
||||
})
|
||||
const SRP1 = ({ clientPublicKey }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/password/srp1", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
clientPublicKey,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return await res.json();
|
||||
} else {
|
||||
console.log("Failed to do the first step of SRP");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default SRP1;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { PATH } from "../../../const.js";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This route send the verification email to the user's email (contains a 6-digit verification code)
|
||||
* @param {*} email
|
||||
*/
|
||||
const sendVerificationEmail = (email) => {
|
||||
fetch(PATH + "/api/v1/signup/email/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
});
|
||||
fetch(PATH + "/api/v1/signup/email/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default sendVerificationEmail;
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import { PATH } from "../../../const.js";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
const token = async (req, res) => {
|
||||
return fetch(PATH + "/api/v1/auth/token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include"
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).token;
|
||||
} else {
|
||||
console.log('Getting a new token failed');
|
||||
}
|
||||
})
|
||||
return fetch(PATH + "/api/v1/auth/token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).token;
|
||||
} else {
|
||||
console.log("Getting a new token failed");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default token;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This route verifies the signup invite link
|
||||
@@ -6,17 +6,17 @@ import { PATH } from "../../../const";
|
||||
* @param {*} code
|
||||
* @returns
|
||||
*/
|
||||
const verifySignupInvite = ({email, code}) => {
|
||||
return fetch(PATH + "/api/v1/invite-org/verify", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
code,
|
||||
}),
|
||||
});
|
||||
const verifySignupInvite = ({ email, code }) => {
|
||||
return fetch(PATH + "/api/v1/invite-org/verify", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
code,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default verifySignupInvite;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PATH } from "../../../const.js";
|
||||
import { PATH } from "~/const";
|
||||
|
||||
/**
|
||||
* This route lets us get the public key of infisical. Th euser doesn't have to be authenticated since this is just the public key.
|
||||
@@ -7,12 +7,12 @@ import { PATH } from "../../../const.js";
|
||||
* @returns
|
||||
*/
|
||||
const publicKeyInfisical = (req, res) => {
|
||||
return fetch(PATH + "/api/v1/key/publicKey/infisical", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
return fetch(PATH + "/api/v1/key/publicKey/infisical", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export default publicKeyInfisical;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient.js";
|
||||
import { PATH } from "../../../const.js";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient.js";
|
||||
|
||||
/**
|
||||
* This function fetches the encrypted secrets from the .env file
|
||||
@@ -8,26 +8,28 @@ import { PATH } from "../../../const.js";
|
||||
* @returns
|
||||
*/
|
||||
const getSecrets = async (workspaceId, env) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" +
|
||||
workspaceId +
|
||||
"?" +
|
||||
new URLSearchParams({
|
||||
environment: env,
|
||||
channel: "web",
|
||||
}
|
||||
), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json());
|
||||
} else {
|
||||
console.log('Failed to get project secrets');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH +
|
||||
"/api/v1/secret/" +
|
||||
workspaceId +
|
||||
"?" +
|
||||
new URLSearchParams({
|
||||
environment: env,
|
||||
channel: "web",
|
||||
}),
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return await res.json();
|
||||
} else {
|
||||
console.log("Failed to get project secrets");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getSecrets;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function uploads the encrypted .env file
|
||||
@@ -8,25 +8,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const uploadSecrets = async ({ workspaceId, secrets, keys, environment }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" + workspaceId, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
secrets,
|
||||
keys,
|
||||
environment,
|
||||
channel: "web",
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to push secrets');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/secret/" + workspaceId, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
secrets,
|
||||
keys,
|
||||
environment,
|
||||
channel: "web",
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to push secrets");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default uploadSecrets;
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
const changeHerokuConfigVars = ({ integrationId, key, secrets }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId + "/sync", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
key,
|
||||
secrets,
|
||||
})
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to sync secrets to Heroku');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/integration/" + integrationId + "/sync",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
key,
|
||||
secrets,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to sync secrets to Heroku");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default changeHerokuConfigVars;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes an integration from a certain project
|
||||
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const deleteIntegration = ({ integrationId }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspace;
|
||||
} else {
|
||||
console.log('Failed to delete an integration');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/integration/" + integrationId,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspace;
|
||||
} else {
|
||||
console.log("Failed to delete an integration");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default deleteIntegration;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes an integration authorization from a certain project
|
||||
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const deleteIntegrationAuth = ({ integrationAuthId }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/" + integrationAuthId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to delete an integration authorization');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/integration-auth/" + integrationAuthId,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to delete an integration authorization");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default deleteIntegrationAuth;
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
const getIntegrationApps = ({ integrationAuthId }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).apps;
|
||||
} else {
|
||||
console.log('Failed to get available apps for an integration');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/integration-auth/" + integrationAuthId + "/apps",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).apps;
|
||||
} else {
|
||||
console.log("Failed to get available apps for an integration");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getIntegrationApps;
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
const getIntegrations = () => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).integrations;
|
||||
} else {
|
||||
console.log('Failed to get project integrations');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration/integrations", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).integrations;
|
||||
} else {
|
||||
console.log("Failed to get project integrations");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getIntegrations;
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route starts the integration after teh default one if gonna set up.
|
||||
* This route starts the integration after teh default one if gonna set up.
|
||||
* @param {*} integrationId
|
||||
* @returns
|
||||
*/
|
||||
const startIntegration = ({ integrationId, appName, environment }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration/" + integrationId, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"update": {
|
||||
app: appName,
|
||||
environment,
|
||||
isActive: true
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to start an integration');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/integration/" + integrationId,
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
update: {
|
||||
app: appName,
|
||||
environment,
|
||||
isActive: true,
|
||||
},
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to start an integration");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default startIntegration;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This is the first step of the change password process (pake)
|
||||
@@ -7,24 +7,26 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const AuthorizeIntegration = ({ workspaceId, code, integration }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/integration-auth/oauth-token", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspaceId,
|
||||
code,
|
||||
integration
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to authorize the integration');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/integration-auth/oauth-token",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspaceId,
|
||||
code,
|
||||
integration,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to authorize the integration");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default AuthorizeIntegration;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets authorizations of a certain project (Heroku, etc.)
|
||||
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getWorkspaceAuthorizations = ({ workspaceId }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/authorizations", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).authorizations;
|
||||
} else {
|
||||
console.log('Failed to get project authorizations');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + workspaceId + "/authorizations",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).authorizations;
|
||||
} else {
|
||||
console.log("Failed to get project authorizations");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getWorkspaceAuthorizations;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets integrations of a certain project (Heroku, etc.)
|
||||
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getWorkspaceIntegrations = ({ workspaceId }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/integrations", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).integrations;
|
||||
} else {
|
||||
console.log('Failed to get the project integrations');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + workspaceId + "/integrations",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).integrations;
|
||||
} else {
|
||||
console.log("Failed to get the project integrations");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getWorkspaceIntegrations;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get info about a certain org
|
||||
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getOrganization = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).organization;
|
||||
} else {
|
||||
console.log('Failed to get org info');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).organization;
|
||||
} else {
|
||||
console.log("Failed to get org info");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getOrganization;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the users in an org.
|
||||
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getOrganizationProjects = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/organization/" + req.orgId + "/workspaces", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspaces;
|
||||
} else {
|
||||
console.log('Failed to get projects for an org');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/organization/" + req.orgId + "/workspaces",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspaces;
|
||||
} else {
|
||||
console.log("Failed to get projects for an org");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getOrganizationProjects;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the current subscription of an org.
|
||||
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getOrganizationSubscriptions = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/subscriptions", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).subscriptions;
|
||||
} else {
|
||||
console.log('Failed to get org subscriptions');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + req.orgId + "/subscriptions",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).subscriptions;
|
||||
} else {
|
||||
console.log("Failed to get org subscriptions");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getOrganizationSubscriptions;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the projects of a certain user in an org.
|
||||
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getOrganizationUserProjects = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspaces;
|
||||
} else {
|
||||
console.log('Failed to get projects of a user in an org');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + req.orgId + "/my-workspaces",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspaces;
|
||||
} else {
|
||||
console.log("Failed to get projects of a user in an org");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getOrganizationUserProjects;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the users in an org.
|
||||
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getOrganizationUsers = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + req.orgId + "/users", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).users;
|
||||
} else {
|
||||
console.log('Failed to get org users');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + req.orgId + "/users",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).users;
|
||||
} else {
|
||||
console.log("Failed to get org users");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getOrganizationUsers;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route redirects the user to the right stripe billing page.
|
||||
@@ -7,20 +7,22 @@ import { PATH } from "../../../const";
|
||||
* @param {*} res
|
||||
* @returns
|
||||
*/
|
||||
const StripeRedirect = ({orgId}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + orgId + "/customer-portal-session", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return window.location.href = (await res.json()).url;
|
||||
} else {
|
||||
console.log('Failed to redirect to Stripe');
|
||||
}
|
||||
})
|
||||
const StripeRedirect = ({ orgId }) => {
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + orgId + "/customer-portal-session",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (window.location.href = (await res.json()).url);
|
||||
} else {
|
||||
console.log("Failed to redirect to Stripe");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default StripeRedirect;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route add an incident contact email to a certain organization
|
||||
@@ -7,22 +7,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const addIncidentContact = (organizationId, email) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to add an incident contact');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to add an incident contact");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default addIncidentContact;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function sends an email invite to a user to join an org
|
||||
@@ -8,23 +8,22 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const addUserToOrg = (email, orgId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/invite-org/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
inviteeEmail: email,
|
||||
organizationId: orgId,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to add a user to an org');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/invite-org/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
inviteeEmail: email,
|
||||
organizationId: orgId,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to add a user to an org");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default addUserToOrg;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes an incident Contact from a certain organization
|
||||
@@ -7,22 +7,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const deleteIncidentContact = (organizaionId, email) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg", {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to delete an incident contact');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + organizaionId + "/incidentContactOrg",
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to delete an incident contact");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default deleteIncidentContact;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function removes a certain member from a certain organization
|
||||
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const deleteUserFromOrganization = (membershipId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/membership-org/" + membershipId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to delete a user from an org');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/membership-org/" + membershipId,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to delete a user from an org");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default deleteUserFromOrganization;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This routes gets all the incident contacts of a certain organization
|
||||
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getIncidentContacts = (organizationId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).incidentContactsOrg;
|
||||
} else {
|
||||
console.log('Failed to get incident contacts');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + organizationId + "/incidentContactOrg",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).incidentContactsOrg;
|
||||
} else {
|
||||
console.log("Failed to get incident contacts");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getIncidentContacts;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the all the orgs of a certain user.
|
||||
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getOrganizations = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).organizations;
|
||||
} else {
|
||||
console.log('Failed to get orgs of a user');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).organizations;
|
||||
} else {
|
||||
console.log("Failed to get orgs of a user");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getOrganizations;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us rename a certain org.
|
||||
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const renameOrg = (orgId, newOrgName) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/organization/" + orgId + "/name", {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: newOrgName,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to rename an organization');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/organization/" + orgId + "/name",
|
||||
{
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: newOrgName,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to rename an organization");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default renameOrg;
|
||||
|
||||
@@ -1,34 +1,41 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets service tokens for a specific user in a project
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const addServiceToken = ({name, workspaceId, environment, expiresIn, publicKey, encryptedKey, nonce}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
workspaceId,
|
||||
environment,
|
||||
expiresIn,
|
||||
publicKey,
|
||||
encryptedKey,
|
||||
nonce
|
||||
})
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).token;
|
||||
} else {
|
||||
console.log('Failed to add service tokens');
|
||||
}
|
||||
})
|
||||
const addServiceToken = ({
|
||||
name,
|
||||
workspaceId,
|
||||
environment,
|
||||
expiresIn,
|
||||
publicKey,
|
||||
encryptedKey,
|
||||
nonce,
|
||||
}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/service-token/", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
workspaceId,
|
||||
environment,
|
||||
expiresIn,
|
||||
publicKey,
|
||||
encryptedKey,
|
||||
nonce,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).token;
|
||||
} else {
|
||||
console.log("Failed to add service tokens");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default addServiceToken;
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets service tokens for a specific user in a project
|
||||
* @param {*} param0
|
||||
* @returns
|
||||
*/
|
||||
const getServiceTokens = ({workspaceId}) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).serviceTokens;
|
||||
} else {
|
||||
console.log('Failed to get service tokens');
|
||||
}
|
||||
})
|
||||
const getServiceTokens = ({ workspaceId }) => {
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + workspaceId + "/service-tokens",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).serviceTokens;
|
||||
} else {
|
||||
console.log("Failed to get service tokens");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getServiceTokens;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route gets the information about a specific user.
|
||||
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getUser = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/user", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).user;
|
||||
} else {
|
||||
console.log('Failed to get user info');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/user", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).user;
|
||||
} else {
|
||||
console.log("Failed to get user info");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getUser;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route registers a certain action for a user
|
||||
@@ -8,23 +8,26 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const checkUserAction = ({ action }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/user-action" +
|
||||
"?" +
|
||||
new URLSearchParams({
|
||||
action,
|
||||
}), {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).userAction;
|
||||
} else {
|
||||
console.log('Failed to check a user action');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH +
|
||||
"/api/v1/user-action" +
|
||||
"?" +
|
||||
new URLSearchParams({
|
||||
action,
|
||||
}),
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).userAction;
|
||||
} else {
|
||||
console.log("Failed to check a user action");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default checkUserAction;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route registers a certain action for a user
|
||||
@@ -7,22 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const registerUserAction = ({ action }) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/user-action", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to register a user action');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/user-action", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to register a user action");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default registerUserAction;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function adds a user to a project
|
||||
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const addUserToWorkspace = (email, workspaceId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json());
|
||||
} else {
|
||||
console.log('Failed to add a user to project');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + workspaceId + "/invite-signup",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return await res.json();
|
||||
} else {
|
||||
console.log("Failed to add a user to project");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default addUserToWorkspace;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function change the access of a user in a certain workspace
|
||||
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const changeUserRoleInWorkspace = (membershipId, role) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId + "/change-role", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
role: role,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to change the user role in a project');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/membership/" + membershipId + "/change-role",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
role: role,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to change the user role in a project");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default changeUserRoleInWorkspace;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route creates a new workspace for a user.
|
||||
@@ -7,23 +7,22 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const createWorkspace = (workspaceName, organizationId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspaceName: workspaceName,
|
||||
organizationId: organizationId,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspace;
|
||||
} else {
|
||||
console.log('Failed to create a project');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
workspaceName: workspaceName,
|
||||
organizationId: organizationId,
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspace;
|
||||
} else {
|
||||
console.log("Failed to create a project");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default createWorkspace;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This function removes a certain member from a certain workspace
|
||||
@@ -7,19 +7,18 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const deleteUserFromWorkspace = (membershipId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to delete a user from a project');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/membership/" + membershipId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to delete a user from a project");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default deleteUserFromWorkspace;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route deletes a specified workspace.
|
||||
@@ -7,19 +7,18 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const deleteWorkspace = (workspaceId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to delete a project');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to delete a project");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default deleteWorkspace;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* Get the latest key pairs from a certain workspace
|
||||
@@ -7,19 +7,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getLatestFileKey = (workspaceId) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId + "/latest", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json());
|
||||
} else {
|
||||
console.log('Failed to get the latest key pairs for a certain project');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/key/" + workspaceId + "/latest",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return await res.json();
|
||||
} else {
|
||||
console.log("Failed to get the latest key pairs for a certain project");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getLatestFileKey;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the information of a certain project.
|
||||
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getWorkspaceInfo = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspace;
|
||||
} else {
|
||||
console.log('Failed to get project info');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + req.workspaceId,
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspace;
|
||||
} else {
|
||||
console.log("Failed to get project info");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getWorkspaceInfo;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the public keys of everyone in your workspace.
|
||||
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getWorkspaceKeys = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId + "/keys", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).publicKeys;
|
||||
} else {
|
||||
console.log('Failed to get the public keys of everyone in the workspace');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + req.workspaceId + "/keys",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).publicKeys;
|
||||
} else {
|
||||
console.log("Failed to get the public keys of everyone in the workspace");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getWorkspaceKeys;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get all the users in the workspace.
|
||||
@@ -8,19 +8,21 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getWorkspaceUsers = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + req.workspaceId + "/users", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).users;
|
||||
} else {
|
||||
console.log('Failed to get Project Users');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + req.workspaceId + "/users",
|
||||
{
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).users;
|
||||
} else {
|
||||
console.log("Failed to get Project Users");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getWorkspaceUsers;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us get the public keys of everyone in your workspace.
|
||||
@@ -8,19 +8,18 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const getWorkspaces = (req, res) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspaces;
|
||||
} else {
|
||||
console.log('Failed to get projects');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace", {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return (await res.json()).workspaces;
|
||||
} else {
|
||||
console.log("Failed to get projects");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default getWorkspaces;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route lets us rename a certain workspace.
|
||||
@@ -8,22 +8,24 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const renameWorkspace = (workspaceId, newWorkspaceName) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/workspace/" + workspaceId + "/name", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: newWorkspaceName,
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to rename a project');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(
|
||||
PATH + "/api/v1/workspace/" + workspaceId + "/name",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: newWorkspaceName,
|
||||
}),
|
||||
}
|
||||
).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to rename a project");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default renameWorkspace;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import SecurityClient from "../../../components/utilities/SecurityClient";
|
||||
import { PATH } from "../../../const";
|
||||
import { PATH } from "~/const";
|
||||
import SecurityClient from "~/utilities/SecurityClient";
|
||||
|
||||
/**
|
||||
* This route uplods the keys in an encrypted format.
|
||||
@@ -10,26 +10,25 @@ import { PATH } from "../../../const";
|
||||
* @returns
|
||||
*/
|
||||
const uploadKeys = (workspaceId, userId, encryptedKey, nonce) => {
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
key: {
|
||||
userId: userId,
|
||||
encryptedKey: encryptedKey,
|
||||
nonce: nonce,
|
||||
},
|
||||
}),
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log('Failed to upload keys for a new user');
|
||||
}
|
||||
})
|
||||
return SecurityClient.fetchCall(PATH + "/api/v1/key/" + workspaceId, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
key: {
|
||||
userId: userId,
|
||||
encryptedKey: encryptedKey,
|
||||
nonce: nonce,
|
||||
},
|
||||
}),
|
||||
}).then(async (res) => {
|
||||
if (res.status == 200) {
|
||||
return res;
|
||||
} else {
|
||||
console.log("Failed to upload keys for a new user");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default uploadKeys;
|
||||
|
||||
+22
-20
@@ -1,31 +1,33 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Head from "next/head";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
|
||||
export default function DashboardRedirect() {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
|
||||
} else {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
}, []);
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
|
||||
} else {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
DashboardRedirect.requireAuth = true;
|
||||
|
||||
+735
-714
File diff suppressed because it is too large
Load Diff
+24
-23
@@ -4,32 +4,33 @@ import { useRouter } from "next/router";
|
||||
const queryString = require("query-string");
|
||||
import AuthorizeIntegration from "./api/integrations/authorizeIntegration";
|
||||
|
||||
|
||||
export default function Heroku() {
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const code = parsedUrl.code;
|
||||
const state = parsedUrl.state;
|
||||
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state == localStorage.getItem("latestCSRFToken")) {
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
code,
|
||||
integration: "heroku"
|
||||
})
|
||||
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
}, []);
|
||||
/**
|
||||
* Here we forward to the default workspace if a user opens this url
|
||||
*/
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
try {
|
||||
if (state == localStorage.getItem("latestCSRFToken")) {
|
||||
await AuthorizeIntegration({
|
||||
workspaceId: localStorage.getItem("projectData.id"),
|
||||
code,
|
||||
integration: "heroku",
|
||||
});
|
||||
router.push("/integrations/" + localStorage.getItem("projectData.id"));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <div></div>
|
||||
return <div></div>;
|
||||
}
|
||||
|
||||
Heroku.requireAuth = true;
|
||||
|
||||
@@ -2,14 +2,12 @@ import React, { useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
export default function Home() {
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(async () => {
|
||||
router.push("/login");
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
router.push("/login");
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 w-screen">
|
||||
</div>
|
||||
);
|
||||
return <div className="bg-bunker-800 w-screen"></div>;
|
||||
}
|
||||
|
||||
+345
-251
@@ -1,278 +1,372 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import {
|
||||
faArrowRight,
|
||||
faCheck,
|
||||
faRotate,
|
||||
faX,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheck, faArrowRight, faRotate, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import ListBox from "../../components/basic/Listbox";
|
||||
import NavHeader from "../../components/navigation/NavHeader";
|
||||
import getIntegrations from "../api/integrations/GetIntegrations";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import ListBox from "~/components/basic/Listbox";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import getSecretsForProject from "~/components/utilities/secrets/getSecretsForProject";
|
||||
import pushKeysIntegration from "~/components/utilities/secrets/pushKeysIntegration";
|
||||
import guidGenerator from "~/utilities/randomId";
|
||||
|
||||
import deleteIntegration from "../api/integrations/DeleteIntegration";
|
||||
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth";
|
||||
import getIntegrationApps from "../api/integrations/GetIntegrationApps";
|
||||
import getIntegrations from "../api/integrations/GetIntegrations";
|
||||
import getWorkspaceAuthorizations from "../api/integrations/getWorkspaceAuthorizations";
|
||||
import getWorkspaceIntegrations from "../api/integrations/getWorkspaceIntegrations";
|
||||
import startIntegration from "../api/integrations/StartIntegration";
|
||||
import deleteIntegration from "../api/integrations/DeleteIntegration";
|
||||
import getSecretsForProject from "../../components/utilities/getSecretsForProject";
|
||||
import pushKeysIntegration from "../../components/utilities/pushKeysIntegration";
|
||||
import deleteIntegrationAuth from "../api/integrations/DeleteIntegrationAuth";
|
||||
import Button from "../../components/basic/buttons/Button";
|
||||
import guidGenerator from "../../components/utilities/randomId";
|
||||
|
||||
const crypto = require("crypto");
|
||||
|
||||
const envMapping = {
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
Development: "dev",
|
||||
Staging: "staging",
|
||||
Production: "prod",
|
||||
Testing: "test",
|
||||
};
|
||||
|
||||
const reverseEnvMapping = {
|
||||
"dev": "Development",
|
||||
"staging": "Staging",
|
||||
"prod": "Production",
|
||||
"test": "Testing"
|
||||
}
|
||||
dev: "Development",
|
||||
staging: "Staging",
|
||||
prod: "Production",
|
||||
test: "Testing",
|
||||
};
|
||||
|
||||
const Integration = ({projectIntegration}) => {
|
||||
const [integrationEnvironment, setIntegrationEnvironment] = useState(reverseEnvMapping[projectIntegration.environment]);
|
||||
const [fileState, setFileState] = useState([]);
|
||||
const [data, setData] = useState();
|
||||
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
|
||||
const router = useRouter();
|
||||
const [apps, setApps] = useState([]);
|
||||
const [integrationApp, setIntegrationApp] = useState(projectIntegration.app ? projectIntegration.app : apps[0]);
|
||||
const Integration = ({ projectIntegration }) => {
|
||||
const [integrationEnvironment, setIntegrationEnvironment] = useState(
|
||||
reverseEnvMapping[projectIntegration.environment]
|
||||
);
|
||||
const [fileState, setFileState] = useState([]);
|
||||
const [data, setData] = useState();
|
||||
const [isKeyAvailable, setIsKeyAvailable] = useState(true);
|
||||
const router = useRouter();
|
||||
const [apps, setApps] = useState([]);
|
||||
const [integrationApp, setIntegrationApp] = useState(
|
||||
projectIntegration.app ? projectIntegration.app : apps[0]
|
||||
);
|
||||
|
||||
useEffect(async () => {
|
||||
const tempHerokuApps = await getIntegrationApps({integrationAuthId: projectIntegration.integrationAuth});
|
||||
const tempHerokuAppNames = tempHerokuApps.map(app => app.name);
|
||||
setApps(tempHerokuAppNames);
|
||||
setIntegrationApp(projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0])
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
const tempHerokuApps = await getIntegrationApps({
|
||||
integrationAuthId: projectIntegration.integrationAuth,
|
||||
});
|
||||
const tempHerokuAppNames = tempHerokuApps.map((app) => app.name);
|
||||
setApps(tempHerokuAppNames);
|
||||
setIntegrationApp(
|
||||
projectIntegration.app ? projectIntegration.app : tempHerokuAppNames[0]
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
|
||||
<div className="flex flex-row">
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
ENVIRONMENT
|
||||
</div>
|
||||
<ListBox
|
||||
data={
|
||||
!projectIntegration.isActive && [
|
||||
"Development",
|
||||
"Staging",
|
||||
"Production",
|
||||
]
|
||||
}
|
||||
selected={integrationEnvironment}
|
||||
onChange={setIntegrationEnvironment}
|
||||
/>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="mx-4 text-gray-400 mt-8"
|
||||
/>
|
||||
<div className="mr-2">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
INTEGRATION
|
||||
</div>
|
||||
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
|
||||
{projectIntegration.integration.charAt(0).toUpperCase() +
|
||||
projectIntegration.integration.slice(1)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
HEROKU APP
|
||||
</div>
|
||||
<ListBox
|
||||
data={!projectIntegration.isActive && apps}
|
||||
selected={integrationApp}
|
||||
onChange={setIntegrationApp}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row mt-6">
|
||||
{projectIntegration.isActive ? (
|
||||
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faRotate}
|
||||
className="text-lg mr-2.5 text-primary animate-spin"
|
||||
/>
|
||||
<div className="text-gray-300 font-semibold">In Sync</div>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
text="Start Integration"
|
||||
onButtonPressed={async () => {
|
||||
const result = await startIntegration({
|
||||
integrationId: projectIntegration._id,
|
||||
environment: envMapping[integrationEnvironment],
|
||||
appName: integrationApp,
|
||||
});
|
||||
if (result?.status == 200) {
|
||||
let currentSecrets = await getSecretsForProject({
|
||||
env: integrationEnvironment,
|
||||
setFileState,
|
||||
setIsKeyAvailable,
|
||||
setData,
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
|
||||
return <div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-row items-center justify-between mb-4">
|
||||
<div className="flex flex-row">
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
ENVIRONMENT
|
||||
</div>
|
||||
<ListBox
|
||||
data={!projectIntegration.isActive && ["Development", "Staging", "Production"]}
|
||||
selected={integrationEnvironment}
|
||||
onChange={setIntegrationEnvironment}
|
||||
/>
|
||||
</div>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="mx-4 text-gray-400 mt-8"
|
||||
/>
|
||||
<div className="mr-2">
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
INTEGRATION
|
||||
</div>
|
||||
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-20 text-sm font-semibold text-gray-300">
|
||||
{projectIntegration.integration.charAt(0).toUpperCase() + projectIntegration.integration.slice(1)}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-gray-400 self-start ml-1 mb-1 text-xs font-semibold tracking-wide">
|
||||
HEROKU APP
|
||||
</div>
|
||||
<ListBox
|
||||
data={!projectIntegration.isActive && apps}
|
||||
selected={integrationApp}
|
||||
onChange={setIntegrationApp}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row mt-6">
|
||||
{projectIntegration.isActive
|
||||
? <div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
|
||||
<FontAwesomeIcon icon={faRotate} className="text-lg mr-2.5 text-primary animate-spin"/>
|
||||
<div className="text-gray-300 font-semibold">In Sync</div>
|
||||
</div>
|
||||
: <Button
|
||||
text="Start Integration"
|
||||
onButtonPressed={async () => {
|
||||
const result = await startIntegration({
|
||||
"integrationId": projectIntegration._id,
|
||||
"environment": envMapping[integrationEnvironment],
|
||||
"appName": integrationApp
|
||||
})
|
||||
if (result?.status == 200) {
|
||||
let currentSecrets = await getSecretsForProject({
|
||||
env: integrationEnvironment,
|
||||
setFileState,
|
||||
setIsKeyAvailable,
|
||||
setData,
|
||||
workspaceId: router.query.id
|
||||
})
|
||||
|
||||
let obj = Object.assign(
|
||||
{},
|
||||
...currentSecrets.map((row) => ({ [row[2]]: row[3] }))
|
||||
);
|
||||
await pushKeysIntegration({obj, integrationId: projectIntegration._id});
|
||||
router.reload();
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
/>
|
||||
}
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
|
||||
<Button
|
||||
onButtonPressed={async () => {
|
||||
await deleteIntegration({integrationId: projectIntegration._id});
|
||||
router.reload();
|
||||
}}
|
||||
color="red"
|
||||
size="icon-md"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
let obj = Object.assign(
|
||||
{},
|
||||
...currentSecrets.map((row) => ({
|
||||
[row[2]]: row[3],
|
||||
}))
|
||||
);
|
||||
await pushKeysIntegration({
|
||||
obj,
|
||||
integrationId: projectIntegration._id,
|
||||
});
|
||||
router.reload();
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
/>
|
||||
)}
|
||||
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
|
||||
<Button
|
||||
onButtonPressed={async () => {
|
||||
await deleteIntegration({
|
||||
integrationId: projectIntegration._id,
|
||||
});
|
||||
router.reload();
|
||||
}}
|
||||
color="red"
|
||||
size="icon-md"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Integrations() {
|
||||
const [integrations, setIntegrations] = useState();
|
||||
const [projectIntegrations, setProjectIntegrations] = useState();
|
||||
const [authorizations, setAuthorizations] = useState();
|
||||
const router = useRouter();
|
||||
const [csrfToken, setCsrfToken] = useState("");
|
||||
const [integrations, setIntegrations] = useState();
|
||||
const [projectIntegrations, setProjectIntegrations] = useState();
|
||||
const [authorizations, setAuthorizations] = useState();
|
||||
const router = useRouter();
|
||||
const [csrfToken, setCsrfToken] = useState("");
|
||||
|
||||
useEffect(async () => {
|
||||
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
|
||||
setCsrfToken(tempCSRFToken);
|
||||
localStorage.setItem("latestCSRFToken", tempCSRFToken);
|
||||
useEffect(async () => {
|
||||
const tempCSRFToken = crypto.randomBytes(16).toString("hex");
|
||||
setCsrfToken(tempCSRFToken);
|
||||
localStorage.setItem("latestCSRFToken", tempCSRFToken);
|
||||
|
||||
let projectAuthorizations = await getWorkspaceAuthorizations({workspaceId: router.query.id});
|
||||
setAuthorizations(projectAuthorizations);
|
||||
let projectAuthorizations = await getWorkspaceAuthorizations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setAuthorizations(projectAuthorizations);
|
||||
|
||||
const projectIntegrations = await getWorkspaceIntegrations({workspaceId: router.query.id});
|
||||
setProjectIntegrations(projectIntegrations);
|
||||
|
||||
try {
|
||||
const integrationsData = await getIntegrations();
|
||||
setIntegrations(integrationsData);
|
||||
} catch (error) {
|
||||
console.log("Error", error);
|
||||
}
|
||||
}, []);
|
||||
const projectIntegrations = await getWorkspaceIntegrations({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setProjectIntegrations(projectIntegrations);
|
||||
|
||||
return integrations ? (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Dashboard</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Manage your .env files in seconds"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||
/>
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
<NavHeader pageName="Project Integrations" isProjectRelated={true}/>
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">
|
||||
Current Project Integrations
|
||||
</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Manage your integrations of Infisical with third-party services.
|
||||
</p>
|
||||
</div>
|
||||
{projectIntegrations.length > 0
|
||||
? projectIntegrations.map(projectIntegration =>
|
||||
<Integration key={guidGenerator()} projectIntegration={projectIntegration} />
|
||||
)
|
||||
: <div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
|
||||
<div className="mb-1">
|
||||
You don't have any integrations set up yet. When you do, they will appear here.
|
||||
</div>
|
||||
<div className="">
|
||||
To start, click on any of the options below. It takes 5 clicks to set up.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">
|
||||
Available Integrations
|
||||
</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Click on the itegration you want to connect. This will let your environment variables flow automatically into selected third-party services.
|
||||
</p>
|
||||
<p className="mr-4 text-xs text-gray-600 mt-1">
|
||||
Note: during an integration with Heroku, for security reasons, it is impossible to maintain end-to-end encryption. In theory, this lets Infisical decrypt yor environment variables. In practice, we can assure you that this will never be done, and it allows us to protect your secrets from bad actors online. The core Infisical service will always stay end-to-end encrypted. With any questions, reach out support@infisical.com.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
|
||||
{Object.keys(integrations).map(integration =>
|
||||
<div className={`relative ${["Heroku"].includes(integrations[integration].name) ? "" : "opacity-50"}`} key={integrations[integration].name} >
|
||||
<a
|
||||
href={`${["Heroku"].includes(integrations[integration].name) ? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}` : "#"}`}
|
||||
rel="noopener"
|
||||
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${["Heroku"].includes(integrations[integration].name) ? "hover:bg-white/10 duration-200 cursor-pointer" : "cursor-default grayscale"}`}>
|
||||
<Image
|
||||
src={`/images/integrations/${integrations[integration].name}.png`}
|
||||
height={100}
|
||||
width={100}
|
||||
alt="integration logo"
|
||||
></Image>
|
||||
{integrations[integration].name.split(" ").length > 2
|
||||
? <div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
|
||||
<div>{integrations[integration].name.split(" ")[0]}</div>
|
||||
<div className="text-base">{integrations[integration].name.split(" ")[1]}{" "}{integrations[integration].name.split(" ")[2]}</div>
|
||||
</div>
|
||||
: <div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">{integrations[integration].name}</div>
|
||||
}
|
||||
|
||||
</a>
|
||||
{["Heroku"].includes(integrations[integration].name) && authorizations.map(authorization => authorization.integration).includes(integrations[integration].name.toLowerCase()) &&
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div onClick={() => {
|
||||
deleteIntegrationAuth({integrationAuthId: authorizations.filter(authorization => authorization.integration == integrations[integration].name.toLowerCase()).map(authorization => authorization._id)[0]});
|
||||
router.reload();
|
||||
}} className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"><FontAwesomeIcon icon={faX} className="text-xs mr-2 py-px"/>Revoke</div>
|
||||
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200"><FontAwesomeIcon icon={faCheck} className="text-xs mr-2"/>Authorized</div>
|
||||
</div>
|
||||
}
|
||||
{!["Heroku"].includes(integrations[integration].name) &&
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">Coming Soon</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="loading animation"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
try {
|
||||
const integrationsData = await getIntegrations();
|
||||
setIntegrations(integrationsData);
|
||||
} catch (error) {
|
||||
console.log("Error", error);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return integrations ? (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Dashboard</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||
<meta
|
||||
name="og:description"
|
||||
content="Infisical a simple end-to-end encrypted platform that enables teams to sync and manage their .env files."
|
||||
/>
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-96 pb-2 h-screen max-h-[calc(100vh-10px)] overflow-y-scroll no-scrollbar no-scrollbar::-webkit-scrollbar">
|
||||
<NavHeader pageName="Project Integrations" isProjectRelated={true} />
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">Current Project Integrations</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Manage your integrations of Infisical with third-party services.
|
||||
</p>
|
||||
</div>
|
||||
{projectIntegrations.length > 0 ? (
|
||||
projectIntegrations.map((projectIntegration) => (
|
||||
<Integration
|
||||
key={guidGenerator()}
|
||||
projectIntegration={projectIntegration}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex flex-col max-w-5xl justify-center bg-white/5 p-6 rounded-md mx-6 mt-8">
|
||||
<div className="relative px-4 flex flex-col text-gray-400 items-center justify-center">
|
||||
<div className="mb-1">
|
||||
You {"don't"} have any integrations set up yet. When you do,
|
||||
they will appear here.
|
||||
</div>
|
||||
<div className="">
|
||||
To start, click on any of the options below. It takes 5 clicks
|
||||
to set up.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
|
||||
<div className="flex flex-row justify-start items-center text-3xl">
|
||||
<p className="font-semibold mr-4">Available Integrations</p>
|
||||
</div>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
Click on the itegration you want to connect. This will let your
|
||||
environment variables flow automatically into selected third-party
|
||||
services.
|
||||
</p>
|
||||
<p className="mr-4 text-xs text-gray-600 mt-1">
|
||||
Note: during an integration with Heroku, for security reasons, it
|
||||
is impossible to maintain end-to-end encryption. In theory, this
|
||||
lets Infisical decrypt yor environment variables. In practice, we
|
||||
can assure you that this will never be done, and it allows us to
|
||||
protect your secrets from bad actors online. The core Infisical
|
||||
service will always stay end-to-end encrypted. With any questions,
|
||||
reach out support@infisical.com.
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-4 grid-cols-3 grid-rows-3 mx-6 mt-4 max-w-5xl">
|
||||
{Object.keys(integrations).map((integration) => (
|
||||
<div
|
||||
className={`relative ${
|
||||
["Heroku"].includes(integrations[integration].name)
|
||||
? ""
|
||||
: "opacity-50"
|
||||
}`}
|
||||
key={integrations[integration].name}
|
||||
>
|
||||
<a
|
||||
href={`${
|
||||
["Heroku"].includes(integrations[integration].name)
|
||||
? `https://id.heroku.com/oauth/authorize?client_id=bc132901-935a-4590-b010-f1857efc380d&response_type=code&scope=write-protected&state=${csrfToken}`
|
||||
: "#"
|
||||
}`}
|
||||
rel="noopener"
|
||||
className={`relative flex flex-row bg-white/5 h-32 rounded-md p-4 items-center ${
|
||||
["Heroku"].includes(integrations[integration].name)
|
||||
? "hover:bg-white/10 duration-200 cursor-pointer"
|
||||
: "cursor-default grayscale"
|
||||
}`}
|
||||
>
|
||||
<Image
|
||||
src={`/images/integrations/${integrations[integration].name}.png`}
|
||||
height={100}
|
||||
width={100}
|
||||
alt="integration logo"
|
||||
></Image>
|
||||
{integrations[integration].name.split(" ").length > 2 ? (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-8 max-w-xs">
|
||||
<div>{integrations[integration].name.split(" ")[0]}</div>
|
||||
<div className="text-base">
|
||||
{integrations[integration].name.split(" ")[1]}{" "}
|
||||
{integrations[integration].name.split(" ")[2]}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-2xl ml-8 max-w-xs">
|
||||
{integrations[integration].name}
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
{["Heroku"].includes(integrations[integration].name) &&
|
||||
authorizations
|
||||
.map((authorization) => authorization.integration)
|
||||
.includes(integrations[integration].name.toLowerCase()) && (
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div
|
||||
onClick={() => {
|
||||
deleteIntegrationAuth({
|
||||
integrationAuthId: authorizations
|
||||
.filter(
|
||||
(authorization) =>
|
||||
authorization.integration ==
|
||||
integrations[integration].name.toLowerCase()
|
||||
)
|
||||
.map((authorization) => authorization._id)[0],
|
||||
});
|
||||
router.reload();
|
||||
}}
|
||||
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-xs mr-2 py-px"
|
||||
/>
|
||||
Revoke
|
||||
</div>
|
||||
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-xs mr-2"
|
||||
/>
|
||||
Authorized
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!["Heroku"].includes(integrations[integration].name) && (
|
||||
<div className="absolute group z-50 top-0 right-0 flex flex-row">
|
||||
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
|
||||
Coming Soon
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="loading animation"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Integrations.requireAuth = true;
|
||||
|
||||
+126
-134
@@ -1,147 +1,139 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import InputField from "../components/basic/InputField";
|
||||
import Error from "../components/basic/Error";
|
||||
import Button from "../components/basic/buttons/Button";
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
import attemptLogin from "../components/utilities/attemptLogin";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { useRouter } from "next/router";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import Error from "~/components/basic/Error";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import attemptLogin from "~/utilities/attemptLogin";
|
||||
|
||||
import getWorkspaces from "./api/workspace/getWorkspaces";
|
||||
|
||||
export default function Login() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation("");
|
||||
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
let userWorkspace;
|
||||
try {
|
||||
const userWorkspaces = await getWorkspaces();
|
||||
userWorkspace = userWorkspaces[0]._id;
|
||||
router.push("/dashboard/" + userWorkspace);
|
||||
} catch (error) {
|
||||
console.log("Error - Not logged in yet");
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* This function check if the user entered the correct credentials and should be allowed to log in.
|
||||
*/
|
||||
const loginCheck = async () => {
|
||||
setIsLoading(true);
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
false,
|
||||
true
|
||||
).then(() => {
|
||||
setTimeout(function () {
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
/**
|
||||
* This function check if the user entered the correct credentials and should be allowed to log in.
|
||||
*/
|
||||
const loginCheck = async () => {
|
||||
setIsLoading(true);
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
false,
|
||||
true
|
||||
).then(() => {
|
||||
setTimeout(function () {
|
||||
setIsLoading(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
|
||||
<Head>
|
||||
<title>{t("auth:meta.login.title")}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta
|
||||
property="og:title"
|
||||
content={t("auth:meta.login.og-title")}
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content={t("auth:meta.login.og-description")}
|
||||
/>
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
|
||||
<Image
|
||||
src="/images/biglogo.png"
|
||||
height={90}
|
||||
width={120}
|
||||
alt="long logo"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
{t("auth:login")}
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<p className="text-md flex justify-center mt-2 text-gray-400">
|
||||
{t("auth:need-account")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
|
||||
<Link href="/signup">
|
||||
<button className="w-full pb-3 hover:opacity-90 duration-200">
|
||||
<u className="font-normal text-md text-sky-500">
|
||||
{t("auth:create-account")}
|
||||
</u>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label={t("common:email")}
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label={t("common:password")}
|
||||
onChangeHandler={setPassword}
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{errorLogin && (
|
||||
<Error text="Your email and/or password are wrong." />
|
||||
)}
|
||||
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
|
||||
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
|
||||
<Button
|
||||
text={t("auth:login")}
|
||||
onButtonPressed={loginCheck}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col justify-start px-6">
|
||||
<Head>
|
||||
<title>{t("auth:meta.login.title")}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
<meta property="og:image" content="/images/message.png" />
|
||||
<meta property="og:title" content={t("auth:meta.login.og-title")} />
|
||||
<meta
|
||||
name="og:description"
|
||||
content={t("auth:meta.login.og-description")}
|
||||
/>
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="flex justify-center mb-8 mt-20 cursor-pointer">
|
||||
<Image
|
||||
src="/images/biglogo.png"
|
||||
height={90}
|
||||
width={120}
|
||||
alt="long logo"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
<div className="bg-bunker w-full max-w-md mx-auto h-7/12 py-4 pt-8 px-6 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl flex justify-center font-semibold text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
{t("auth:login")}
|
||||
</p>
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<p className="text-md flex justify-center mt-2 text-gray-400">
|
||||
{t("auth:need-account")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full md:pb-4 max-h-24 max-w-md mx-auto">
|
||||
<Link href="/signup">
|
||||
<button className="w-full pb-3 hover:opacity-90 duration-200">
|
||||
<u className="font-normal text-md text-sky-500">
|
||||
{t("auth:create-account")}
|
||||
</u>
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg mt-4 md:mt-0 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label={t("common:email")}
|
||||
onChangeHandler={setEmail}
|
||||
type="email"
|
||||
value={email}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg md:mt-2 mt-6 max-h-24 md:max-h-28">
|
||||
<InputField
|
||||
label={t("common:password")}
|
||||
onChangeHandler={setPassword}
|
||||
type="password"
|
||||
value={password}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
{errorLogin && <Error text="Your email and/or password are wrong." />}
|
||||
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
|
||||
<div className="text-l mt-6 m-8 px-8 py-3 text-lg">
|
||||
<Button
|
||||
text={t("auth:login")}
|
||||
onButtonPressed={loginCheck}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||
<p className="text-gray-400">I may have <Link href="/login"><u className="text-sky-500 cursor-pointer">forgotten my password.</u></Link></p>
|
||||
</div> */}
|
||||
</div>
|
||||
{false && (
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon
|
||||
icon={faWarning}
|
||||
className="ml-2 mr-6 text-6xl"
|
||||
/>
|
||||
</div>
|
||||
{false && (
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-300 rounded-md max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-6 text-6xl" />
|
||||
|
||||
{t("auth:maintenance-alert")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
{t("auth:maintenance-alert")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,24 @@
|
||||
import React from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
export default function NoProjects() {
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
|
||||
<FontAwesomeIcon icon={faFolderOpen} className="text-7xl mb-8 w-full px-auto" />
|
||||
<div className="max-w-md">
|
||||
You are not part of any projects in this organization yet. When you do, they will appear here.
|
||||
</div>
|
||||
<div className="max-w-md mt-4">
|
||||
Create a new project, or ask
|
||||
other organization members to give you neccessary permissions.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center text-gray-300 text-lg text-center w-11/12 mr-auto">
|
||||
<FontAwesomeIcon
|
||||
icon={faFolderOpen}
|
||||
className="text-7xl mb-8 w-full px-auto"
|
||||
/>
|
||||
<div className="max-w-md">
|
||||
You are not part of any projects in this organization yet. When you do,
|
||||
they will appear here.
|
||||
</div>
|
||||
<div className="max-w-md mt-4">
|
||||
Create a new project, or ask other organization members to give you
|
||||
neccessary permissions.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NoProjects.requireAuth = true;
|
||||
|
||||
@@ -2,21 +2,21 @@ import React from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
export default function Activity() {
|
||||
return (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
|
||||
<Head>
|
||||
<title>Request a New Invite</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen">
|
||||
<p className="text-6xl">Oops.</p>
|
||||
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
|
||||
<p className="text-xl">Ask the administrator for a new one.</p>
|
||||
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
|
||||
Note: If it still doesn't work, please reach out to us at
|
||||
support@infisical.com
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-between">
|
||||
<Head>
|
||||
<title>Request a New Invite</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-col items-center justify-center text-gray-200 h-screen w-screen">
|
||||
<p className="text-6xl">Oops.</p>
|
||||
<p className="mt-2 mb-1 text-xl">Your invite has expired. </p>
|
||||
<p className="text-xl">Ask the administrator for a new one.</p>
|
||||
<p className="text-md mt-8 text-gray-600 max-w-sm text-center">
|
||||
Note: If it still {"doesn't work"}, please reach out to us at
|
||||
support@infisical.com
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,103 +1,109 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Plan from "../../../components/billing/Plan";
|
||||
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription"
|
||||
import getOrganizationUsers from "../../api/organization/GetOrgUsers"
|
||||
import NavHeader from "../../../components/navigation/NavHeader";
|
||||
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "../../../components/utilities/config";
|
||||
|
||||
import Plan from "~/components/billing/Plan";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import { STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_STARTER } from "~/utilities/config";
|
||||
|
||||
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
|
||||
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
|
||||
|
||||
export default function SettingsBilling() {
|
||||
let [currentPlan, setCurrentPlan] = useState("");
|
||||
let [numUsers, setNumUsers] = useState("");
|
||||
let [currentPlan, setCurrentPlan] = useState("");
|
||||
let [numUsers, setNumUsers] = useState("");
|
||||
|
||||
const plans = [
|
||||
{
|
||||
key: 1,
|
||||
name: "Starter",
|
||||
price: "Free",
|
||||
priceExplanation: "up to 5 team members",
|
||||
text: "Manage any project with 5 members for free!",
|
||||
subtext: "$5 per member/month afterwards.",
|
||||
buttonTextMain: "Downgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_STARTER,
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: "Professional",
|
||||
price: "$9",
|
||||
priceExplanation: "/member/month",
|
||||
subtext: "Includes unlimited projects & members.",
|
||||
text: "Keep up with key management as you grow.",
|
||||
buttonTextMain: "Upgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_PRO,
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: "Enterprise",
|
||||
price: "Custom Pricing",
|
||||
text: "More control for advanced organizations.",
|
||||
buttonTextMain: "Schedule a Demo",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: false,
|
||||
},
|
||||
];
|
||||
const plans = [
|
||||
{
|
||||
key: 1,
|
||||
name: "Starter",
|
||||
price: "Free",
|
||||
priceExplanation: "up to 5 team members",
|
||||
text: "Manage any project with 5 members for free!",
|
||||
subtext: "$5 per member/month afterwards.",
|
||||
buttonTextMain: "Downgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_STARTER,
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: "Professional",
|
||||
price: "$9",
|
||||
priceExplanation: "/member/month",
|
||||
subtext: "Includes unlimited projects & members.",
|
||||
text: "Keep up with key management as you grow.",
|
||||
buttonTextMain: "Upgrade",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: currentPlan == STRIPE_PRODUCT_PRO,
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: "Enterprise",
|
||||
price: "Custom Pricing",
|
||||
text: "More control for advanced organizations.",
|
||||
buttonTextMain: "Schedule a Demo",
|
||||
buttonTextSecondary: "Learn More",
|
||||
current: false,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(async () => {
|
||||
const subscriptions = await getOrganizationSubscriptions({orgId: localStorage.getItem("orgData.id")});
|
||||
setCurrentPlan(subscriptions.data[0].plan.product)
|
||||
const orgUsers = await getOrganizationUsers({orgId: localStorage.getItem("orgData.id")})
|
||||
setNumUsers(orgUsers.length)
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings - Billing</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Usage & Billing"/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Usage & Billing
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your organization's subscription here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<p className="text-xl font-semibold">Subscription</p>
|
||||
<div className="flex flex-row mt-4 overflow-x-auto">
|
||||
{plans.map((plan) => (
|
||||
<Plan
|
||||
key={plan.name}
|
||||
plan={plan}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<p className='text-xl font-bold mt-12'>Current Usage</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">{numUsers}</p>
|
||||
<p className="text-gray-300">{numUsers > 1 ? "Organization members" : "Organization member"}</p>
|
||||
</div>
|
||||
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setNumUsers(orgUsers.length);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings - Billing</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Usage & Billing" />
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Usage & Billing
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your {"organization's"} subscription here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<p className="text-xl font-semibold">Subscription</p>
|
||||
<div className="flex flex-row mt-4 overflow-x-auto">
|
||||
{plans.map((plan) => (
|
||||
<Plan key={plan.name} plan={plan} />
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xl font-bold mt-12">Current Usage</p>
|
||||
<div className="flex flex-row">
|
||||
<div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">{numUsers}</p>
|
||||
<p className="text-gray-300">
|
||||
{numUsers > 1
|
||||
? "Organization members"
|
||||
: "Organization member"}
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="mr-4 mt-8 text-gray-300 w-60 pt-6 pb-10 rounded-md bg-white/5 flex justify-center items-center flex flex-col">
|
||||
<p className="text-6xl font-bold">1 </p>
|
||||
<p className="text-gray-300">Organization projects</p>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsBilling.requireAuth = true;
|
||||
|
||||
+318
-346
@@ -1,372 +1,344 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useRouter } from "next/router";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import {
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faX,
|
||||
faMagnifyingGlass,
|
||||
faPlus,
|
||||
faX,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||
import InputField from "../../../components/basic/InputField";
|
||||
import getWorkspaces from "../../api/workspace/getWorkspaces";
|
||||
import AddIncidentContactDialog from "../../../components/basic/dialog/AddIncidentContactDialog";
|
||||
import getIncidentContacts from "../../api/organization/getIncidentContacts";
|
||||
import deleteIncidentContact from "../../api/organization/deleteIncidentContact";
|
||||
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
|
||||
import AddUserDialog from "../../../components/basic/dialog/AddUserDialog";
|
||||
import UserTable from "../../../components/basic/table/UserTable";
|
||||
import getUser from "../../api/user/getUser";
|
||||
import guidGenerator from "../../../components/utilities/randomId";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import AddIncidentContactDialog from "~/components/basic/dialog/AddIncidentContactDialog";
|
||||
import AddUserDialog from "~/components/basic/dialog/AddUserDialog";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import UserTable from "~/components/basic/table/UserTable";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import guidGenerator from "~/utilities/randomId";
|
||||
|
||||
import addUserToOrg from "../../api/organization/addUserToOrg";
|
||||
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
|
||||
import renameOrg from "../../api/organization/renameOrg";
|
||||
import deleteIncidentContact from "../../api/organization/deleteIncidentContact";
|
||||
import getIncidentContacts from "../../api/organization/getIncidentContacts";
|
||||
import getOrganization from "../../api/organization/GetOrg";
|
||||
import getOrganizationSubscriptions from "../../api/organization/GetOrgSubscription";
|
||||
import NavHeader from "../../../components/navigation/NavHeader";
|
||||
import Button from "../../../components/basic/buttons/Button";
|
||||
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import getOrganizationUsers from "../../api/organization/GetOrgUsers";
|
||||
import renameOrg from "../../api/organization/renameOrg";
|
||||
import getUser from "../../api/user/getUser";
|
||||
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
|
||||
import getWorkspaces from "../../api/workspace/getWorkspaces";
|
||||
|
||||
export default function SettingsOrg() {
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [emailUser, setEmailUser] = useState("");
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] =
|
||||
useState("");
|
||||
const [searchUsers, setSearchUsers] = useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
|
||||
useState(false);
|
||||
const [isAddUserOpen, setIsAddUserOpen] = useState(
|
||||
router.asPath.split("?")[1] == "invite"
|
||||
);
|
||||
const [incidentContacts, setIncidentContacts] = useState([]);
|
||||
const [searchIncidentContact, setSearchIncidentContact] = useState("");
|
||||
const [userList, setUserList] = useState();
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
let workspaceIdTemp;
|
||||
const [email, setEmail] = useState("");
|
||||
const [currentPlan, setCurrentPlan] = useState("");
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [emailUser, setEmailUser] = useState("");
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState("");
|
||||
const [searchUsers, setSearchUsers] = useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddIncidentContactOpen, setIsAddIncidentContactOpen] =
|
||||
useState(false);
|
||||
const [isAddUserOpen, setIsAddUserOpen] = useState(
|
||||
router.asPath.split("?")[1] == "invite"
|
||||
);
|
||||
const [incidentContacts, setIncidentContacts] = useState([]);
|
||||
const [searchIncidentContact, setSearchIncidentContact] = useState("");
|
||||
const [userList, setUserList] = useState();
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
let workspaceIdTemp;
|
||||
const [email, setEmail] = useState("");
|
||||
const [currentPlan, setCurrentPlan] = useState("");
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(async () => {
|
||||
let org = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let orgData = org;
|
||||
setOrgName(orgData.name);
|
||||
let incidentContactsData = await getIncidentContacts(
|
||||
localStorage.getItem("orgData.id")
|
||||
);
|
||||
useEffect(async () => {
|
||||
let org = await getOrganization({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
let orgData = org;
|
||||
setOrgName(orgData.name);
|
||||
let incidentContactsData = await getIncidentContacts(
|
||||
localStorage.getItem("orgData.id")
|
||||
);
|
||||
|
||||
setIncidentContacts(
|
||||
incidentContactsData?.map((contact) => contact.email)
|
||||
);
|
||||
setIncidentContacts(incidentContactsData?.map((contact) => contact.email));
|
||||
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
|
||||
workspaceIdTemp = router.query.id;
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setUserList(
|
||||
orgUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email:
|
||||
user.user?.email == null
|
||||
? user.inviteEmail
|
||||
: user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey,
|
||||
}))
|
||||
);
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
}, []);
|
||||
workspaceIdTemp = router.query.id;
|
||||
let orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setUserList(
|
||||
orgUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey,
|
||||
}))
|
||||
);
|
||||
const subscriptions = await getOrganizationSubscriptions({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setCurrentPlan(subscriptions.data[0].plan.product);
|
||||
}, []);
|
||||
|
||||
const modifyOrgName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setOrgName(newName);
|
||||
};
|
||||
const modifyOrgName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setOrgName(newName);
|
||||
};
|
||||
|
||||
const submitChanges = (newOrgName) => {
|
||||
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
const submitChanges = (newOrgName) => {
|
||||
renameOrg(localStorage.getItem("orgData.id"), newOrgName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
|
||||
function closeAddUserModal() {
|
||||
setIsAddUserOpen(false);
|
||||
}
|
||||
function closeAddUserModal() {
|
||||
setIsAddUserOpen(false);
|
||||
}
|
||||
|
||||
function closeAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(false);
|
||||
}
|
||||
function closeAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(false);
|
||||
}
|
||||
|
||||
function openAddUserModal() {
|
||||
setIsAddUserOpen(true);
|
||||
}
|
||||
function openAddUserModal() {
|
||||
setIsAddUserOpen(true);
|
||||
}
|
||||
|
||||
function openAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(true);
|
||||
}
|
||||
function openAddIncidentContactModal() {
|
||||
setIsAddIncidentContactOpen(true);
|
||||
}
|
||||
|
||||
async function submitAddUserModal(email) {
|
||||
await addUserToOrg(email, localStorage.getItem("orgData.id"));
|
||||
setEmail("");
|
||||
setIsAddUserOpen(false);
|
||||
router.reload();
|
||||
}
|
||||
async function submitAddUserModal(email) {
|
||||
await addUserToOrg(email, localStorage.getItem("orgData.id"));
|
||||
setEmail("");
|
||||
setIsAddUserOpen(false);
|
||||
router.reload();
|
||||
}
|
||||
|
||||
const deleteIncidentContactFully = (incidentContact) => {
|
||||
setIncidentContacts(
|
||||
incidentContacts.filter((contact) => contact != incidentContact)
|
||||
);
|
||||
deleteIncidentContact(
|
||||
localStorage.getItem("orgData.id"),
|
||||
incidentContact
|
||||
);
|
||||
};
|
||||
const deleteIncidentContactFully = (incidentContact) => {
|
||||
setIncidentContacts(
|
||||
incidentContacts.filter((contact) => contact != incidentContact)
|
||||
);
|
||||
deleteIncidentContact(localStorage.getItem("orgData.id"), incidentContact);
|
||||
};
|
||||
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>{t("settings:meta.org.head-title")}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName={t("settings:meta.org.title")} />
|
||||
<AddIncidentContactDialog
|
||||
isOpen={isAddIncidentContactOpen}
|
||||
closeModal={closeAddIncidentContactModal}
|
||||
workspaceId={workspaceId}
|
||||
incidentContacts={incidentContacts}
|
||||
setIncidentContacts={setIncidentContacts}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
{t("settings:meta.org.title")}
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
{t("settings:meta.org.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-end pb-4">
|
||||
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
{t("common:display-name")}
|
||||
</p>
|
||||
<InputField
|
||||
// label="Organization Name"
|
||||
onChangeHandler={modifyOrgName}
|
||||
type="varName"
|
||||
value={orgName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div
|
||||
className={`flex justify-start max-w-sm mt-4 mb-2`}
|
||||
>
|
||||
<Button
|
||||
text={t("common:save-changes")}
|
||||
onButtonPressed={() =>
|
||||
submitChanges(orgName)
|
||||
}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled={t("common:saved")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<p className="font-semibold mr-4 text-white text-xl">
|
||||
{t("settings:org-members")}
|
||||
</p>
|
||||
<p className="mr-4 text-gray-400 mt-2 mb-2">
|
||||
{t("settings:org-members-description")}
|
||||
</p>
|
||||
<AddUserDialog
|
||||
isOpen={isAddUserOpen}
|
||||
closeModal={closeAddUserModal}
|
||||
submitModal={submitAddUserModal}
|
||||
email={emailUser}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmailUser}
|
||||
currentPlan={currentPlan}
|
||||
orgName={orgName}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="pb-1 w-full flex flex-row items-start max-w-6xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchUsers}
|
||||
onChange={(e) =>
|
||||
setSearchUsers(e.target.value)
|
||||
}
|
||||
placeholder={t(
|
||||
"settings:search-members"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
|
||||
<Button
|
||||
text={t("settings:add-member")}
|
||||
onButtonPressed={openAddUserModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{userList && (
|
||||
<div className="overflow-y-auto max-w-6xl w-full">
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
myUser={personalEmail}
|
||||
filter={searchUsers.toLowerCase()}
|
||||
resendInvite={submitAddUserModal}
|
||||
isOrg={true}
|
||||
// onClick={openDeleteModal}
|
||||
// deleteUser={deleteMembership}
|
||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>{t("settings:meta.org.head-title")}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName={t("settings:meta.org.title")} />
|
||||
<AddIncidentContactDialog
|
||||
isOpen={isAddIncidentContactOpen}
|
||||
closeModal={closeAddIncidentContactModal}
|
||||
workspaceId={workspaceId}
|
||||
incidentContacts={incidentContacts}
|
||||
setIncidentContacts={setIncidentContacts}
|
||||
/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
{t("settings:meta.org.title")}
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
{t("settings:meta.org.description")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-end pb-4">
|
||||
<div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
{t("common:display-name")}
|
||||
</p>
|
||||
<InputField
|
||||
// label="Organization Name"
|
||||
onChangeHandler={modifyOrgName}
|
||||
type="varName"
|
||||
value={orgName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
|
||||
<Button
|
||||
text={t("common:save-changes")}
|
||||
onButtonPressed={() => submitChanges(orgName)}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled={t("common:saved")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<p className="font-semibold mr-4 text-white text-xl">
|
||||
{t("settings:org-members")}
|
||||
</p>
|
||||
<p className="mr-4 text-gray-400 mt-2 mb-2">
|
||||
{t("settings:org-members-description")}
|
||||
</p>
|
||||
<AddUserDialog
|
||||
isOpen={isAddUserOpen}
|
||||
closeModal={closeAddUserModal}
|
||||
submitModal={submitAddUserModal}
|
||||
email={emailUser}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmailUser}
|
||||
currentPlan={currentPlan}
|
||||
orgName={orgName}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="pb-1 w-full flex flex-row items-start max-w-6xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchUsers}
|
||||
onChange={(e) => setSearchUsers(e.target.value)}
|
||||
placeholder={t("settings:search-members")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start">
|
||||
<Button
|
||||
text={t("settings:add-member")}
|
||||
onButtonPressed={openAddUserModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{userList && (
|
||||
<div className="overflow-y-auto max-w-6xl w-full">
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
myUser={personalEmail}
|
||||
filter={searchUsers.toLowerCase()}
|
||||
resendInvite={submitAddUserModal}
|
||||
isOrg={true}
|
||||
// onClick={openDeleteModal}
|
||||
// deleteUser={deleteMembership}
|
||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
{t("settings:incident-contacts")}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mb-2 min-w-max">
|
||||
{t(
|
||||
"settings:incident-contacts-description"
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
|
||||
<Button
|
||||
text={t("common:add-contact")}
|
||||
onButtonPressed={
|
||||
openAddIncidentContactModal
|
||||
}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchIncidentContact}
|
||||
onChange={(e) =>
|
||||
setSearchIncidentContact(e.target.value)
|
||||
}
|
||||
placeholder={t("settings:search")}
|
||||
/>
|
||||
</div>
|
||||
{incidentContacts?.filter((email) =>
|
||||
email.includes(searchIncidentContact)
|
||||
).length > 0 ? (
|
||||
incidentContacts
|
||||
.filter((email) =>
|
||||
email.includes(searchIncidentContact)
|
||||
)
|
||||
.map((contact) => (
|
||||
<div
|
||||
key={guidGenerator()}
|
||||
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
|
||||
>
|
||||
<p className="text-gray-300">
|
||||
{contact}
|
||||
</p>
|
||||
<div className="opacity-50 hover:opacity-100 duration-200">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteIncidentContactFully(
|
||||
contact
|
||||
)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
|
||||
<p className="text-gray-400">
|
||||
{t("settings:no-incident-contacts")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
{t("settings:incident-contacts")}
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 mb-2 min-w-max">
|
||||
{t("settings:incident-contacts-description")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 mb-2 min-w-max flex flex-row items-end justify-end justify-center">
|
||||
<Button
|
||||
text={t("common:add-contact")}
|
||||
onButtonPressed={openAddIncidentContactModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-12 w-full max-w-5xl bg-white/5 mt-2 flex items-center rounded-t-md flex flwex-row items-center">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-tl-md py-4 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-tr-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchIncidentContact}
|
||||
onChange={(e) => setSearchIncidentContact(e.target.value)}
|
||||
placeholder={t("settings:search")}
|
||||
/>
|
||||
</div>
|
||||
{incidentContacts?.filter((email) =>
|
||||
email.includes(searchIncidentContact)
|
||||
).length > 0 ? (
|
||||
incidentContacts
|
||||
.filter((email) => email.includes(searchIncidentContact))
|
||||
.map((contact) => (
|
||||
<div
|
||||
key={guidGenerator()}
|
||||
className="flex flex-row items-center justify-between max-w-5xl px-4 py-3 hover:bg-white/5 border-t border-gray-600 w-full"
|
||||
>
|
||||
<p className="text-gray-300">{contact}</p>
|
||||
<div className="opacity-50 hover:opacity-100 duration-200">
|
||||
<Button
|
||||
onButtonPressed={() =>
|
||||
deleteIncidentContactFully(contact)
|
||||
}
|
||||
color="red"
|
||||
size="icon-sm"
|
||||
icon={faX}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="w-full flex flex-row justify-center mt-6 max-w-4xl ml-6">
|
||||
<p className="text-gray-400">
|
||||
{t("settings:no-incident-contacts")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
|
||||
{/* <div className="border-l border-red pb-4 pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pt-2 max-w-6xl">
|
||||
<p className="text-xl font-bold text-red">
|
||||
Danger Zone
|
||||
</p>
|
||||
@@ -401,11 +373,11 @@ export default function SettingsOrg() {
|
||||
have more than one.
|
||||
</p>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsOrg.requireAuth = true;
|
||||
|
||||
@@ -1,61 +1,60 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheck, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import InputField from "../../../components/basic/InputField";
|
||||
import getUser from "../../api/user/getUser";
|
||||
import NavHeader from "../../../components/navigation/NavHeader";
|
||||
import passwordCheck from "../../../components/utilities/checks/PasswordCheck";
|
||||
import changePassword from "../../../components/utilities/changePassword";
|
||||
import issueBackupKey from "../../../components/utilities/issueBackupKey";
|
||||
import Button from "../../../components/basic/buttons/Button";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import changePassword from "~/components/utilities/cryptography/changePassword";
|
||||
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
|
||||
import passwordCheck from "~/utilities/checks/PasswordCheck";
|
||||
|
||||
import getUser from "../../api/user/getUser";
|
||||
|
||||
export default function PersonalSettings() {
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
const [personalName, setPersonalName] = useState("");
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [backupPassword, setBackupPassword] = useState("");
|
||||
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
const [personalName, setPersonalName] = useState("");
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const [currentPasswordError, setCurrentPasswordError] = useState(false);
|
||||
const [currentPassword, setCurrentPassword] = useState("");
|
||||
const [newPassword, setNewPassword] = useState("");
|
||||
const [backupPassword, setBackupPassword] = useState("");
|
||||
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
|
||||
useEffect(async () => {
|
||||
let user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
setPersonalName(user.firstName + " " + user.lastName);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
let user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
setPersonalName(user.firstName + " " + user.lastName);
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Personal Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Personal Settings" isProjectRelated={false}/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Personal Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your personal information here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md flex flex-col items-end pb-4">
|
||||
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Personal Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<div className="flex flex-row">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Personal Settings" isProjectRelated={false} />
|
||||
<div className="flex flex-row justify-between items-center ml-6 mt-8 mb-6 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Personal Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
View and manage your personal information here.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50 mr-6 max-w-5xl">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md flex flex-col items-end pb-4">
|
||||
{/* <div className="bg-white/5 rounded-md px-6 py-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<p className="font-semibold mr-4 text-gray-200 text-xl mb-2">
|
||||
Display Name
|
||||
@@ -99,143 +98,223 @@ export default function PersonalSettings() {
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Change password
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-xl w-full">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={(password) => {
|
||||
setCurrentPassword(password);
|
||||
}}
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
isRequired
|
||||
error={currentPasswordError}
|
||||
errorText="The current password may be wrong"
|
||||
/>
|
||||
<div className="py-2"></div>
|
||||
<InputField
|
||||
label="New Password"
|
||||
onChangeHandler={(password) => {
|
||||
setNewPassword(password);
|
||||
passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
false
|
||||
);
|
||||
}}
|
||||
type="password"
|
||||
value={newPassword}
|
||||
isRequired
|
||||
error={passwordErrorLength && passwordErrorLowerCase && passwordErrorNumber}
|
||||
/>
|
||||
</div>
|
||||
{(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber) ? <div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
|
||||
<div className={`text-gray-400 text-sm mb-1`}>Password should contain at least:</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLength
|
||||
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
|
||||
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
|
||||
<div className={`${passwordErrorLength ? "text-gray-400" : "text-gray-600"} text-sm`}>14 characters</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLowerCase
|
||||
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
|
||||
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
|
||||
<div className={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}>1 lowercase character</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorNumber
|
||||
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
|
||||
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
|
||||
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>1 number</div>
|
||||
</div>
|
||||
</div> : <div className="py-2"></div>}
|
||||
<div className="flex flex-row items-center mt-3 w-52 pr-3">
|
||||
<Button
|
||||
text="Change Password"
|
||||
onButtonPressed={() => {
|
||||
if (!passwordErrorLength && !passwordErrorLowerCase && !passwordErrorNumber) {
|
||||
changePassword(
|
||||
personalEmail,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
setCurrentPasswordError,
|
||||
setPasswordChanged,
|
||||
setCurrentPassword,
|
||||
setNewPassword
|
||||
)
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={(newPassword != "") && (currentPassword != "") && !(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber)}
|
||||
textDisabled="Change Password"
|
||||
/>
|
||||
<FontAwesomeIcon icon={faCheck} className={`ml-4 text-primary text-3xl ${passwordChanged ? "opacity-100" : "opacity-0"} duration-300`}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Change password
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-xl w-full">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={(password) => {
|
||||
setCurrentPassword(password);
|
||||
}}
|
||||
type="password"
|
||||
value={currentPassword}
|
||||
isRequired
|
||||
error={currentPasswordError}
|
||||
errorText="The current password may be wrong"
|
||||
/>
|
||||
<div className="py-2"></div>
|
||||
<InputField
|
||||
label="New Password"
|
||||
onChangeHandler={(password) => {
|
||||
setNewPassword(password);
|
||||
passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
false
|
||||
);
|
||||
}}
|
||||
type="password"
|
||||
value={newPassword}
|
||||
isRequired
|
||||
error={
|
||||
passwordErrorLength &&
|
||||
passwordErrorLowerCase &&
|
||||
passwordErrorNumber
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber ? (
|
||||
<div className="w-full mt-3 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md max-w-xl mb-2">
|
||||
<div className={`text-gray-400 text-sm mb-1`}>
|
||||
Password should contain at least:
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength ? "text-gray-400" : "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
14 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase
|
||||
? "text-gray-400"
|
||||
: "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 lowercase character
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 number
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
)}
|
||||
<div className="flex flex-row items-center mt-3 w-52 pr-3">
|
||||
<Button
|
||||
text="Change Password"
|
||||
onButtonPressed={() => {
|
||||
if (
|
||||
!passwordErrorLength &&
|
||||
!passwordErrorLowerCase &&
|
||||
!passwordErrorNumber
|
||||
) {
|
||||
changePassword(
|
||||
personalEmail,
|
||||
currentPassword,
|
||||
newPassword,
|
||||
setCurrentPasswordError,
|
||||
setPasswordChanged,
|
||||
setCurrentPassword,
|
||||
setNewPassword
|
||||
);
|
||||
}
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={
|
||||
newPassword != "" &&
|
||||
currentPassword != "" &&
|
||||
!(
|
||||
passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber
|
||||
)
|
||||
}
|
||||
textDisabled="Change Password"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
passwordChanged ? "opacity-100" : "opacity-0"
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Emergency Kit
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 min-w-max">
|
||||
Your Emergency Kit contains the information you’ll need to sign in to your Infisical account.
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
|
||||
Only the latest issued Emergency Kit remains valid. To get a new Emergency Kit, verify your password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-xl mb-4">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={setBackupPassword}
|
||||
type="password"
|
||||
value={backupPassword}
|
||||
isRequired
|
||||
error={backupKeyError}
|
||||
errorText="The current password is wrong"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center mt-3 w-full w-60">
|
||||
<Button
|
||||
text="Download Emergency Kit"
|
||||
onButtonPressed={() => {
|
||||
issueBackupKey({
|
||||
email: personalEmail,
|
||||
password: backupPassword,
|
||||
personalName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued
|
||||
});
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={backupPassword != ""}
|
||||
textDisabled="Download Emergency Kit"
|
||||
/>
|
||||
<FontAwesomeIcon icon={faCheck} className={`ml-4 text-primary text-3xl ${backupKeyIssued ? "opacity-100" : "opacity-0"} duration-300`}/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
<div className="bg-white/5 rounded-md px-6 pt-5 pb-6 mt-4 flex flex-col items-start flex flex-col items-start w-full mb-6">
|
||||
<div className="flex flex-row max-w-5xl justify-between items-center w-full">
|
||||
<div className="flex flex-col justify-between w-full max-w-3xl">
|
||||
<p className="text-xl font-semibold mb-3 min-w-max">
|
||||
Emergency Kit
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 min-w-max">
|
||||
Your Emergency Kit contains the information you’ll need to
|
||||
sign in to your Infisical account.
|
||||
</p>
|
||||
<p className="text-sm text-mineshaft-300 mb-5 min-w-max">
|
||||
Only the latest issued Emergency Kit remains valid. To get a
|
||||
new Emergency Kit, verify your password.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full max-w-xl mb-4">
|
||||
<InputField
|
||||
label="Current Password"
|
||||
onChangeHandler={setBackupPassword}
|
||||
type="password"
|
||||
value={backupPassword}
|
||||
isRequired
|
||||
error={backupKeyError}
|
||||
errorText="The current password is wrong"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-row items-center mt-3 w-full w-60">
|
||||
<Button
|
||||
text="Download Emergency Kit"
|
||||
onButtonPressed={() => {
|
||||
issueBackupKey({
|
||||
email: personalEmail,
|
||||
password: backupPassword,
|
||||
personalName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued,
|
||||
});
|
||||
}}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={backupPassword != ""}
|
||||
textDisabled="Download Emergency Kit"
|
||||
/>
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className={`ml-4 text-primary text-3xl ${
|
||||
backupKeyIssued ? "opacity-100" : "opacity-0"
|
||||
} duration-300`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
PersonalSettings.requireAuth = true;
|
||||
|
||||
@@ -1,206 +1,205 @@
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import Head from "next/head";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import { faCheck, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import InputField from "../../../components/basic/InputField";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import AddServiceTokenDialog from "~/components/basic/dialog/AddServiceTokenDialog";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import ServiceTokenTable from "~/components/basic/table/ServiceTokenTable";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
|
||||
import getServiceTokens from "../../api/serviceToken/getServiceTokens";
|
||||
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
|
||||
import getWorkspaces from "../../api/workspace/getWorkspaces";
|
||||
import renameWorkspace from "../../api/workspace/renameWorkspace";
|
||||
import deleteWorkspace from "../../api/workspace/deleteWorkspace";
|
||||
import NavHeader from "../../../components/navigation/NavHeader";
|
||||
import Button from "../../../components/basic/buttons/Button";
|
||||
import ServiceTokenTable from "../../../components/basic/table/ServiceTokenTable";
|
||||
import getServiceTokens from "../../api/serviceToken/getServiceTokens"
|
||||
import AddServiceTokenDialog from "../../../components/basic/dialog/AddServiceTokenDialog";
|
||||
|
||||
export default function SettingsBasic() {
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const [serviceTokens, setServiceTokens] = useState([]);
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] =
|
||||
useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] = useState(false);
|
||||
const [buttonReady, setButtonReady] = useState(false);
|
||||
const router = useRouter();
|
||||
const [workspaceName, setWorkspaceName] = useState("");
|
||||
const [serviceTokens, setServiceTokens] = useState([]);
|
||||
const [workspaceToBeDeletedName, setWorkspaceToBeDeletedName] = useState("");
|
||||
const [workspaceId, setWorkspaceId] = useState("");
|
||||
const [isAddOpen, setIsAddOpen] = useState(false);
|
||||
let [isAddServiceTokenDialogOpen, setIsAddServiceTokenDialogOpen] =
|
||||
useState(false);
|
||||
|
||||
useEffect(async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
userWorkspaces.map((userWorkspace) => {
|
||||
if (userWorkspace._id == router.query.id) {
|
||||
setWorkspaceName(userWorkspace.name);
|
||||
}
|
||||
});
|
||||
let tempServiceTokens = await getServiceTokens({workspaceId: router.query.id});
|
||||
setServiceTokens(tempServiceTokens);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
userWorkspaces.map((userWorkspace) => {
|
||||
if (userWorkspace._id == router.query.id) {
|
||||
setWorkspaceName(userWorkspace.name);
|
||||
}
|
||||
});
|
||||
let tempServiceTokens = await getServiceTokens({
|
||||
workspaceId: router.query.id,
|
||||
});
|
||||
setServiceTokens(tempServiceTokens);
|
||||
}, []);
|
||||
|
||||
const modifyWorkspaceName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setWorkspaceName(newName);
|
||||
};
|
||||
const modifyWorkspaceName = (newName) => {
|
||||
setButtonReady(true);
|
||||
setWorkspaceName(newName);
|
||||
};
|
||||
|
||||
const submitChanges = (newWorkspaceName) => {
|
||||
renameWorkspace(router.query.id, newWorkspaceName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
const submitChanges = (newWorkspaceName) => {
|
||||
renameWorkspace(router.query.id, newWorkspaceName);
|
||||
setButtonReady(false);
|
||||
};
|
||||
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
useEffect(async () => {
|
||||
setWorkspaceId(router.query.id);
|
||||
}, []);
|
||||
|
||||
function closeAddModal() {
|
||||
setIsAddOpen(false);
|
||||
}
|
||||
function closeAddModal() {
|
||||
setIsAddOpen(false);
|
||||
}
|
||||
|
||||
function openAddModal() {
|
||||
setIsAddOpen(true);
|
||||
}
|
||||
function openAddModal() {
|
||||
setIsAddOpen(true);
|
||||
}
|
||||
|
||||
const closeAddServiceTokenModal = () => {
|
||||
setIsAddServiceTokenDialogOpen(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
const closeAddServiceTokenModal = () => {
|
||||
setIsAddServiceTokenDialogOpen(false);
|
||||
};
|
||||
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
/**
|
||||
* This function deleted a workspace.
|
||||
* It first checks if there is more than one workspace aviable. Otherwise, it doesn't delete
|
||||
* It then checks if the name of the workspace to be deleted is correct. Otherwise, it doesn't delete.
|
||||
* It then deletes the workspace and forwards the user to another aviable workspace.
|
||||
*/
|
||||
const executeDeletingWorkspace = async () => {
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<AddServiceTokenDialog
|
||||
isOpen={isAddServiceTokenDialogOpen}
|
||||
workspaceId={router.query.id}
|
||||
closeModal={closeAddServiceTokenModal}
|
||||
workspaceName={workspaceName}
|
||||
/>
|
||||
<div className="flex flex-row mr-6 max-w-5xl">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Project Settings" isProjectRelated={true}/>
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Project Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
These settings only apply to the currently
|
||||
selected Project.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-start">
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
|
||||
<p className="text-xl font-semibold mb-4">
|
||||
Display Name
|
||||
</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<InputField
|
||||
onChangeHandler={
|
||||
modifyWorkspaceName
|
||||
}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div
|
||||
className={`flex justify-start max-w-sm mt-4 mb-2`}
|
||||
>
|
||||
<Button
|
||||
text="Save Changes"
|
||||
onButtonPressed={() => submitChanges(workspaceName)}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled="Saved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<p className="text-xl font-semibold self-start">
|
||||
Project ID
|
||||
</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start mt-4">
|
||||
To integrate Infisical into your code
|
||||
base and get automatic injection of
|
||||
environmental variables, you should use
|
||||
the following Project ID.
|
||||
</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start">
|
||||
For more guidance, including code
|
||||
snipets for various languages and
|
||||
frameworks, see{" "}
|
||||
<a
|
||||
href="https://infisical.com/docs/getting-started/introduction"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="text-primary hover:opacity-80 duration-200"
|
||||
>
|
||||
Infisical Docs.
|
||||
</a>
|
||||
</p>
|
||||
<div className="max-h-28 w-ful">
|
||||
<InputField
|
||||
type="varName"
|
||||
value={router.query.id}
|
||||
placeholder=""
|
||||
isRequired
|
||||
static
|
||||
text="This is your project's auto-generated unique identifier. It can't be changed."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
<p className="text-xl font-semibold mb-3">
|
||||
Service Tokens
|
||||
</p>
|
||||
<p className="text-base text-gray-400 mb-4">
|
||||
Every service token is specific to you, a certain project and a certain environment within this project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-48">
|
||||
<Button
|
||||
text="Add New Token"
|
||||
onButtonPressed={() => {setIsAddServiceTokenDialogOpen(true)}}
|
||||
color="mineshaft"
|
||||
icon={faPlus}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ServiceTokenTable data={serviceTokens} workspaceName={workspaceName}/>
|
||||
</div>
|
||||
if (userWorkspaces.length > 1) {
|
||||
if (
|
||||
userWorkspaces.filter(
|
||||
(workspace) => workspace._id == router.query.id
|
||||
)[0].name == workspaceToBeDeletedName
|
||||
) {
|
||||
await deleteWorkspace(router.query.id);
|
||||
let userWorkspaces = await getWorkspaces();
|
||||
router.push("/dashboard/" + userWorkspaces[0]._id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
|
||||
return (
|
||||
<div className="bg-bunker-800 max-h-screen flex flex-col justify-between text-white">
|
||||
<Head>
|
||||
<title>Settings</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<AddServiceTokenDialog
|
||||
isOpen={isAddServiceTokenDialogOpen}
|
||||
workspaceId={router.query.id}
|
||||
closeModal={closeAddServiceTokenModal}
|
||||
workspaceName={workspaceName}
|
||||
/>
|
||||
<div className="flex flex-row mr-6 max-w-5xl">
|
||||
<div className="w-full max-h-screen pb-2 overflow-y-auto">
|
||||
<NavHeader pageName="Project Settings" isProjectRelated={true} />
|
||||
<div className="flex flex-row justify-between items-center ml-6 my-8 text-xl max-w-5xl">
|
||||
<div className="flex flex-col justify-start items-start text-3xl">
|
||||
<p className="font-semibold mr-4 text-gray-200">
|
||||
Project Settings
|
||||
</p>
|
||||
<p className="font-normal mr-4 text-gray-400 text-base">
|
||||
These settings only apply to the currently selected Project.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col ml-6 text-mineshaft-50">
|
||||
<div className="flex flex-col">
|
||||
<div className="min-w-md mt-2 flex flex-col items-start">
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-4 flex flex-col items-start flex flex-col items-start w-full mb-6 pt-2">
|
||||
<p className="text-xl font-semibold mb-4">Display Name</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto">
|
||||
<InputField
|
||||
onChangeHandler={modifyWorkspaceName}
|
||||
type="varName"
|
||||
value={workspaceName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-start w-full">
|
||||
<div className={`flex justify-start max-w-sm mt-4 mb-2`}>
|
||||
<Button
|
||||
text="Save Changes"
|
||||
onButtonPressed={() => submitChanges(workspaceName)}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
active={buttonReady}
|
||||
iconDisabled={faCheck}
|
||||
textDisabled="Saved"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-2 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4">
|
||||
<p className="text-xl font-semibold self-start">Project ID</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start mt-4">
|
||||
To integrate Infisical into your code base and get automatic
|
||||
injection of environmental variables, you should use the
|
||||
following Project ID.
|
||||
</p>
|
||||
<p className="text-base text-gray-400 font-normal self-start">
|
||||
For more guidance, including code snipets for various
|
||||
languages and frameworks, see{" "}
|
||||
{/* eslint-disable-next-line react/jsx-no-target-blank */}
|
||||
<a
|
||||
href="https://infisical.com/docs/getting-started/introduction"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
className="text-primary hover:opacity-80 duration-200"
|
||||
>
|
||||
Infisical Docs.
|
||||
</a>
|
||||
</p>
|
||||
<div className="max-h-28 w-ful">
|
||||
<InputField
|
||||
type="varName"
|
||||
value={router.query.id}
|
||||
placeholder=""
|
||||
isRequired
|
||||
static
|
||||
text="This is your project's auto-generated unique identifier. It can't be changed."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 flex flex-col items-start flex flex-col items-start w-full mt-4 mb-4 pt-2">
|
||||
<div className="flex flex-row justify-between w-full">
|
||||
<div className="flex flex-col w-full">
|
||||
<p className="text-xl font-semibold mb-3">
|
||||
Service Tokens
|
||||
</p>
|
||||
<p className="text-base text-gray-400 mb-4">
|
||||
Every service token is specific to you, a certain
|
||||
project and a certain environment within this project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-48">
|
||||
<Button
|
||||
text="Add New Token"
|
||||
onButtonPressed={() => {
|
||||
setIsAddServiceTokenDialogOpen(true);
|
||||
}}
|
||||
color="mineshaft"
|
||||
icon={faPlus}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ServiceTokenTable
|
||||
data={serviceTokens}
|
||||
workspaceName={workspaceName}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* <div className="bg-white/5 rounded-md px-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-6 pt-6">
|
||||
<p className="text-xl font-semibold self-start">
|
||||
Project Environments
|
||||
</p>
|
||||
@@ -235,47 +234,42 @@ export default function SettingsBasic() {
|
||||
</div>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
|
||||
<p className="text-xl font-bold text-red">
|
||||
Danger Zone
|
||||
</p>
|
||||
<p className="mt-2 text-md text-gray-400">
|
||||
As soon as you delete this project, you will not
|
||||
be able to undo it. This will immediately remove
|
||||
all the keys. If you still want to do that,
|
||||
please enter the name of the project below.
|
||||
</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
|
||||
<InputField
|
||||
label="Project to be Deleted"
|
||||
onChangeHandler={
|
||||
setWorkspaceToBeDeletedName
|
||||
}
|
||||
type="varName"
|
||||
value={workspaceToBeDeletedName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={executeDeletingWorkspace}
|
||||
>
|
||||
Delete Project
|
||||
</button>
|
||||
<p className="mt-0.5 ml-1 text-xs text-gray-500">
|
||||
Note: You can only delete a project in case you
|
||||
have more than one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-white/5 rounded-md px-6 pt-6 pb-6 border-l border-red pl-6 flex flex-col items-start flex flex-col items-start w-full mb-6 mt-4 pb-4 pt-2">
|
||||
<p className="text-xl font-bold text-red">Danger Zone</p>
|
||||
<p className="mt-2 text-md text-gray-400">
|
||||
As soon as you delete this project, you will not be able to undo
|
||||
it. This will immediately remove all the keys. If you still want
|
||||
to do that, please enter the name of the project below.
|
||||
</p>
|
||||
<div className="max-h-28 w-full max-w-md mr-auto mt-4">
|
||||
<InputField
|
||||
label="Project to be Deleted"
|
||||
onChangeHandler={setWorkspaceToBeDeletedName}
|
||||
type="varName"
|
||||
value={workspaceToBeDeletedName}
|
||||
placeholder=""
|
||||
isRequired
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="max-w-md mt-6 w-full inline-flex justify-center rounded-md border border-transparent bg-gray-800 px-4 py-2.5 text-sm font-medium text-gray-400 hover:bg-red hover:text-white hover:font-semibold hover:text-semibold duration-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
onClick={executeDeletingWorkspace}
|
||||
>
|
||||
Delete Project
|
||||
</button>
|
||||
<p className="mt-0.5 ml-1 text-xs text-gray-500">
|
||||
Note: You can only delete a project in case you have more than
|
||||
one.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsBasic.requireAuth = true;
|
||||
|
||||
+496
-525
File diff suppressed because it is too large
Load Diff
+340
-280
@@ -1,19 +1,19 @@
|
||||
import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
|
||||
import InputField from "../components/basic/InputField";
|
||||
import Button from "../components/basic/buttons/Button";
|
||||
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
|
||||
import Aes256Gcm from "../components/aes-256-gcm";
|
||||
import passwordCheck from "../components/utilities/checks/PasswordCheck";
|
||||
import attemptLogin from "../components/utilities/attemptLogin";
|
||||
import { useRouter } from "next/router";
|
||||
import { faCheck, faWarning, faX } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCheck, faX, faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import issueBackupKey from "../components/utilities/issueBackupKey";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import InputField from "~/components/basic/InputField";
|
||||
import Aes256Gcm from "~/components/utilities/cryptography/aes-256-gcm";
|
||||
import issueBackupKey from "~/components/utilities/cryptography/issueBackupKey";
|
||||
import attemptLogin from "~/utilities/attemptLogin";
|
||||
import passwordCheck from "~/utilities/checks/PasswordCheck";
|
||||
|
||||
import completeAccountInformationSignupInvite from "./api/auth/CompleteAccountInformationSignupInvite";
|
||||
import verifySignupInvite from "./api/auth/VerifySignupInvite";
|
||||
|
||||
const nacl = require("tweetnacl");
|
||||
@@ -23,263 +23,323 @@ const client = new jsrp.client();
|
||||
const queryString = require("query-string");
|
||||
|
||||
export default function SignupInvite() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [firstNameError, setFirstNameError] = useState(false);
|
||||
const [lastNameError, setLastNameError] = useState(false);
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const [email, setEmail] = useState(parsedUrl.to);
|
||||
const token = parsedUrl.token;
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [verificationToken, setVerificationToken] = useState();
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
const [password, setPassword] = useState("");
|
||||
const [firstName, setFirstName] = useState("");
|
||||
const [lastName, setLastName] = useState("");
|
||||
const [firstNameError, setFirstNameError] = useState(false);
|
||||
const [lastNameError, setLastNameError] = useState(false);
|
||||
const [passwordErrorLength, setPasswordErrorLength] = useState(false);
|
||||
const [passwordErrorNumber, setPasswordErrorNumber] = useState(false);
|
||||
const [passwordErrorLowerCase, setPasswordErrorLowerCase] = useState(false);
|
||||
const router = useRouter();
|
||||
const parsedUrl = queryString.parse(router.asPath.split("?")[1]);
|
||||
const [email, setEmail] = useState(parsedUrl.to);
|
||||
const token = parsedUrl.token;
|
||||
const [errorLogin, setErrorLogin] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||
const [verificationToken, setVerificationToken] = useState();
|
||||
const [backupKeyIssued, setBackupKeyIssued] = useState(false);
|
||||
|
||||
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
|
||||
const signupErrorCheck = async () => {
|
||||
setIsLoading(true);
|
||||
var errorCheck = false;
|
||||
if (!firstName) {
|
||||
setFirstNameError(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setFirstNameError(false);
|
||||
}
|
||||
if (!lastName) {
|
||||
setLastNameError(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setLastNameError(false);
|
||||
}
|
||||
errorCheck = passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck
|
||||
);
|
||||
// Verifies if the information that the users entered (name, workspace) is there, and if the password matched the criteria.
|
||||
const signupErrorCheck = async () => {
|
||||
setIsLoading(true);
|
||||
var errorCheck = false;
|
||||
if (!firstName) {
|
||||
setFirstNameError(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setFirstNameError(false);
|
||||
}
|
||||
if (!lastName) {
|
||||
setLastNameError(true);
|
||||
errorCheck = true;
|
||||
} else {
|
||||
setLastNameError(false);
|
||||
}
|
||||
errorCheck = passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
errorCheck
|
||||
);
|
||||
|
||||
if (!errorCheck) {
|
||||
// Generate a random pair of a public and a private key
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array);
|
||||
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array);
|
||||
if (!errorCheck) {
|
||||
// Generate a random pair of a public and a private key
|
||||
const pair = nacl.box.keyPair();
|
||||
const secretKeyUint8Array = pair.secretKey;
|
||||
const publicKeyUint8Array = pair.publicKey;
|
||||
const PRIVATE_KEY = nacl.util.encodeBase64(secretKeyUint8Array);
|
||||
const PUBLIC_KEY = nacl.util.encodeBase64(publicKeyUint8Array);
|
||||
|
||||
const { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||
PRIVATE_KEY,
|
||||
password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
|
||||
);
|
||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||
const { ciphertext, iv, tag } = Aes256Gcm.encrypt(
|
||||
PRIVATE_KEY,
|
||||
password
|
||||
.slice(0, 32)
|
||||
.padStart(
|
||||
32 + (password.slice(0, 32).length - new Blob([password]).size),
|
||||
"0"
|
||||
)
|
||||
);
|
||||
localStorage.setItem("PRIVATE_KEY", PRIVATE_KEY);
|
||||
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
client.createVerifier(async (err, result) => {
|
||||
const response = await completeAccountInformationSignupInvite({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey: PUBLIC_KEY,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
token: verificationToken
|
||||
});
|
||||
client.init(
|
||||
{
|
||||
username: email,
|
||||
password: password,
|
||||
},
|
||||
async () => {
|
||||
client.createVerifier(async (err, result) => {
|
||||
const response = await completeAccountInformationSignupInvite({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
publicKey: PUBLIC_KEY,
|
||||
ciphertext,
|
||||
iv,
|
||||
tag,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
token: verificationToken,
|
||||
});
|
||||
|
||||
// if everything works, go the main dashboard page.
|
||||
if (!errorCheck && response.status == "200") {
|
||||
response = await response.json();
|
||||
// if everything works, go the main dashboard page.
|
||||
if (!errorCheck && response.status == "200") {
|
||||
response = await response.json();
|
||||
|
||||
localStorage.setItem("publicKey", PUBLIC_KEY);
|
||||
localStorage.setItem(
|
||||
"encryptedPrivateKey",
|
||||
ciphertext
|
||||
);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
localStorage.setItem("publicKey", PUBLIC_KEY);
|
||||
localStorage.setItem("encryptedPrivateKey", ciphertext);
|
||||
localStorage.setItem("iv", iv);
|
||||
localStorage.setItem("tag", tag);
|
||||
|
||||
try {
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
false,
|
||||
false
|
||||
);
|
||||
setStep(3);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
console.log("Error", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
try {
|
||||
await attemptLogin(
|
||||
email,
|
||||
password,
|
||||
setErrorLogin,
|
||||
router,
|
||||
false,
|
||||
false
|
||||
);
|
||||
setStep(3);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
console.log("Error", error);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
} else {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
const stepConfirmEmail = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Confirm your email
|
||||
</p>
|
||||
<Image
|
||||
src="/images/envelope.svg"
|
||||
height={262}
|
||||
width={410}
|
||||
alt="verify email"
|
||||
></Image>
|
||||
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto text-lg py-1 text-center md:text-left">
|
||||
<Button
|
||||
text="Confirm Email"
|
||||
onButtonPressed={async () => {
|
||||
const response = await verifySignupInvite({
|
||||
email,
|
||||
code: token
|
||||
});
|
||||
if (response.status == 200) {
|
||||
setVerificationToken((await response.json()).token)
|
||||
setStep(2);
|
||||
} else {
|
||||
console.log("ERROR", response)
|
||||
router.push("/requestnewinvite")
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
const stepConfirmEmail = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold mb-8 flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Confirm your email
|
||||
</p>
|
||||
<Image
|
||||
src="/images/envelope.svg"
|
||||
height={262}
|
||||
width={410}
|
||||
alt="verify email"
|
||||
></Image>
|
||||
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto text-lg py-1 text-center md:text-left">
|
||||
<Button
|
||||
text="Confirm Email"
|
||||
onButtonPressed={async () => {
|
||||
const response = await verifySignupInvite({
|
||||
email,
|
||||
code: token,
|
||||
});
|
||||
if (response.status == 200) {
|
||||
setVerificationToken((await response.json()).token);
|
||||
setStep(2);
|
||||
} else {
|
||||
console.log("ERROR", response);
|
||||
router.push("/requestnewinvite");
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Because this is the invite signup - we directly go to the last step of signup (email is already verified)
|
||||
const main = (
|
||||
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16">
|
||||
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Almost there!
|
||||
</p>
|
||||
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
|
||||
<InputField
|
||||
label="First Name"
|
||||
onChangeHandler={setFirstName}
|
||||
type="name"
|
||||
value={firstName}
|
||||
isRequired
|
||||
errorText="Please input your first name."
|
||||
error={firstNameError}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
|
||||
<InputField
|
||||
label="Last Name"
|
||||
onChangeHandler={setLastName}
|
||||
type="name"
|
||||
value={lastName}
|
||||
isRequired
|
||||
errorText="Please input your last name."
|
||||
error={lastNameError}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
|
||||
<InputField
|
||||
label="Password"
|
||||
onChangeHandler={(password) => {
|
||||
setPassword(password);
|
||||
passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
false
|
||||
);
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
isRequired
|
||||
error={passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase}
|
||||
/>
|
||||
{(passwordErrorLength || passwordErrorLowerCase || passwordErrorNumber) ? <div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
|
||||
<div className={`text-gray-400 text-sm mb-1`}>Password should contain at least:</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLength
|
||||
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
|
||||
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
|
||||
<div className={`${passwordErrorLength ? "text-gray-400" : "text-gray-600"} text-sm`}>14 characters</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLowerCase
|
||||
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
|
||||
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
|
||||
<div className={`${passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"} text-sm`}>1 lowercase character</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorNumber
|
||||
? <FontAwesomeIcon icon={faX} className="text-md text-red mr-2.5"/>
|
||||
: <FontAwesomeIcon icon={faCheck} className="text-md text-primary mr-2"/>}
|
||||
<div className={`${passwordErrorNumber ? "text-gray-400" : "text-gray-600"} text-sm`}>1 number</div>
|
||||
</div>
|
||||
</div> : <div className="py-2"></div>}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-md mx-auto text-lg">
|
||||
<Button
|
||||
text="Sign Up"
|
||||
onButtonPressed={() => {
|
||||
signupErrorCheck();
|
||||
}}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
// Because this is the invite signup - we directly go to the last step of signup (email is already verified)
|
||||
const main = (
|
||||
<div className="bg-bunker w-max mx-auto h-7/12 py-10 px-8 rounded-xl drop-shadow-xl mb-32 md:mb-16">
|
||||
<p className="text-4xl font-bold flex justify-center mb-6 text-gray-400 mx-8 md:mx-16 text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Almost there!
|
||||
</p>
|
||||
<div className="relative z-0 flex items-center justify-end w-full md:p-2 rounded-lg max-h-24">
|
||||
<InputField
|
||||
label="First Name"
|
||||
onChangeHandler={setFirstName}
|
||||
type="name"
|
||||
value={firstName}
|
||||
isRequired
|
||||
errorText="Please input your first name."
|
||||
error={firstNameError}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24">
|
||||
<InputField
|
||||
label="Last Name"
|
||||
onChangeHandler={setLastName}
|
||||
type="name"
|
||||
value={lastName}
|
||||
isRequired
|
||||
errorText="Please input your last name."
|
||||
error={lastNameError}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex flex-col items-center justify-center w-full md:p-2 rounded-lg max-h-60">
|
||||
<InputField
|
||||
label="Password"
|
||||
onChangeHandler={(password) => {
|
||||
setPassword(password);
|
||||
passwordCheck(
|
||||
password,
|
||||
setPasswordErrorLength,
|
||||
setPasswordErrorNumber,
|
||||
setPasswordErrorLowerCase,
|
||||
false
|
||||
);
|
||||
}}
|
||||
type="password"
|
||||
value={password}
|
||||
isRequired
|
||||
error={
|
||||
passwordErrorLength && passwordErrorNumber && passwordErrorLowerCase
|
||||
}
|
||||
/>
|
||||
{passwordErrorLength ||
|
||||
passwordErrorLowerCase ||
|
||||
passwordErrorNumber ? (
|
||||
<div className="w-full mt-4 bg-white/5 px-2 flex flex-col items-start py-2 rounded-md">
|
||||
<div className={`text-gray-400 text-sm mb-1`}>
|
||||
Password should contain at least:
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLength ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLength ? "text-gray-400" : "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
14 characters
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorLowerCase ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorLowerCase ? "text-gray-400" : "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 lowercase character
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row justify-start items-center ml-1">
|
||||
{passwordErrorNumber ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faX}
|
||||
className="text-md text-red mr-2.5"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-md text-primary mr-2"
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={`${
|
||||
passwordErrorNumber ? "text-gray-400" : "text-gray-600"
|
||||
} text-sm`}
|
||||
>
|
||||
1 number
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="py-2"></div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col items-center justify-center w-full md:px-4 md:py-5 mt-2 px-2 py-3 max-h-24 max-w-md mx-auto text-lg">
|
||||
<Button
|
||||
text="Sign Up"
|
||||
onButtonPressed={() => {
|
||||
signupErrorCheck();
|
||||
}}
|
||||
loading={isLoading}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
const step4 = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Save your Emergency Kit
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
|
||||
<div>If you get locked out of your account, your Emergency Kit is the only way to sign in.</div>
|
||||
<div className="mt-3">We recommend you download it and keep it somewhere safe.</div>
|
||||
</div>
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl"/>
|
||||
It contains your Secret Key which we cannot access or recover for you if you lose it.
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-6 md:mt-8 py-1 text-lg text-center md:text-left">
|
||||
<Button
|
||||
text="Download PDF"
|
||||
onButtonPressed={async () => {
|
||||
await issueBackupKey({
|
||||
email,
|
||||
password,
|
||||
personalName: (firstName + " " + lastName),
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued
|
||||
});
|
||||
router.push("/dashboard/");
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
{/* <div
|
||||
// Step 4 of the sign up process (download the emergency kit pdf)
|
||||
const step4 = (
|
||||
<div className="bg-bunker flex flex-col items-center w-full max-w-xs md:max-w-lg mx-auto h-7/12 py-8 px-4 md:px-6 mx-1 mb-36 md:mb-16 rounded-xl drop-shadow-xl">
|
||||
<p className="text-4xl text-center font-semibold flex justify-center text-transparent bg-clip-text bg-gradient-to-br from-sky-400 to-primary">
|
||||
Save your Emergency Kit
|
||||
</p>
|
||||
<div className="flex flex-col items-center justify-center w-full mt-4 md:mt-8 max-w-md text-gray-400 text-md rounded-md px-2">
|
||||
<div>
|
||||
If you get locked out of your account, your Emergency Kit is the only
|
||||
way to sign in.
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
We recommend you download it and keep it somewhere safe.
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full p-2 flex flex-row items-center bg-white/10 text-gray-400 rounded-md max-w-xs md:max-w-md mx-auto mt-4">
|
||||
<FontAwesomeIcon icon={faWarning} className="ml-2 mr-4 text-4xl" />
|
||||
It contains your Secret Key which we cannot access or recover for you if
|
||||
you lose it.
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center w-3/4 md:w-full md:p-2 max-h-28 max-w-xs md:max-w-md mx-auto mt-6 md:mt-8 py-1 text-lg text-center md:text-left">
|
||||
<Button
|
||||
text="Download PDF"
|
||||
onButtonPressed={async () => {
|
||||
await issueBackupKey({
|
||||
email,
|
||||
password,
|
||||
personalName: firstName + " " + lastName,
|
||||
setBackupKeyError,
|
||||
setBackupKeyIssued,
|
||||
});
|
||||
router.push("/dashboard/");
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
{/* <div
|
||||
className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer"
|
||||
onClick={() => {
|
||||
if (localStorage.getItem("projectData.id")) {
|
||||
@@ -291,27 +351,27 @@ export default function SignupInvite() {
|
||||
>
|
||||
Later
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
|
||||
<Head>
|
||||
<title>Sign Up</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer">
|
||||
<Image
|
||||
src="/images/biglogo.png"
|
||||
height={90}
|
||||
width={120}
|
||||
alt="Infisical Wide Logo"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
{step == 1 ? stepConfirmEmail : step == 2 ? main : step4}
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
|
||||
<Head>
|
||||
<title>Sign Up</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<Link href="/">
|
||||
<div className="flex justify-center mb-2 md:mb-4 opacity-80 cursor-pointer">
|
||||
<Image
|
||||
src="/images/biglogo.png"
|
||||
height={90}
|
||||
width={120}
|
||||
alt="Infisical Wide Logo"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
{step == 1 ? stepConfirmEmail : step == 2 ? main : step4}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+183
-189
@@ -1,217 +1,211 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import UserTable from "../../components/basic/table/UserTable";
|
||||
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers";
|
||||
import guidGenerator from "../../components/utilities/randomId";
|
||||
import AddProjectMemberDialog from "../../components/basic/dialog/AddProjectMemberDialog";
|
||||
// import DeleteUserDialog from '../../components/basic/dialog/DeleteUserDialog';
|
||||
import addUserToWorkspace from "../api/workspace/addUserToWorkspace";
|
||||
import getUser from "../api/user/getUser";
|
||||
import uploadKeys from "../api/workspace/uploadKeys";
|
||||
import getOrganizationUsers from "../api/organization/GetOrgUsers";
|
||||
import NavHeader from "../../components/navigation/NavHeader";
|
||||
import Button from "../../components/basic/buttons/Button";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
import useTranslation from "next-translate/useTranslation";
|
||||
import { faMagnifyingGlass, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import Button from "~/components/basic/buttons/Button";
|
||||
import AddProjectMemberDialog from "~/components/basic/dialog/AddProjectMemberDialog";
|
||||
import UserTable from "~/components/basic/table/UserTable";
|
||||
import NavHeader from "~/components/navigation/NavHeader";
|
||||
import guidGenerator from "~/utilities/randomId";
|
||||
|
||||
import getOrganizationUsers from "../api/organization/GetOrgUsers";
|
||||
import getUser from "../api/user/getUser";
|
||||
// import DeleteUserDialog from '~/components/basic/dialog/DeleteUserDialog';
|
||||
import addUserToWorkspace from "../api/workspace/addUserToWorkspace";
|
||||
import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers";
|
||||
import uploadKeys from "../api/workspace/uploadKeys";
|
||||
|
||||
// #TODO: Update all the workspaceIds
|
||||
const crypto = require("crypto");
|
||||
const {
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} = require("../../components/utilities/crypto");
|
||||
decryptAssymmetric,
|
||||
encryptAssymmetric,
|
||||
} = require("../../components/utilities/cryptography/crypto");
|
||||
const nacl = require("tweetnacl");
|
||||
nacl.util = require("tweetnacl-util");
|
||||
|
||||
export default function Users() {
|
||||
let [isAddOpen, setIsAddOpen] = useState(false);
|
||||
// let [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
|
||||
let [email, setEmail] = useState("");
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
const [searchUsers, setSearchUsers] = useState("");
|
||||
let [isAddOpen, setIsAddOpen] = useState(false);
|
||||
// let [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
// let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false);
|
||||
let [email, setEmail] = useState("");
|
||||
const [personalEmail, setPersonalEmail] = useState("");
|
||||
const [searchUsers, setSearchUsers] = useState("");
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const router = useRouter();
|
||||
let workspaceId;
|
||||
const router = useRouter();
|
||||
let workspaceId;
|
||||
|
||||
function closeAddModal() {
|
||||
setIsAddOpen(false);
|
||||
}
|
||||
function closeAddModal() {
|
||||
setIsAddOpen(false);
|
||||
}
|
||||
|
||||
// function closeDeleteModal() {
|
||||
// setIsDeleteOpen(false);
|
||||
// }
|
||||
// function closeDeleteModal() {
|
||||
// setIsDeleteOpen(false);
|
||||
// }
|
||||
|
||||
// function deleteMembership(userId) {
|
||||
// deleteUserFromWorkspace(userId, router.query.id)
|
||||
// }
|
||||
// function deleteMembership(userId) {
|
||||
// deleteUserFromWorkspace(userId, router.query.id)
|
||||
// }
|
||||
|
||||
// function openDeleteModal() {
|
||||
// setIsDeleteOpen(true);
|
||||
// }
|
||||
// function openDeleteModal() {
|
||||
// setIsDeleteOpen(true);
|
||||
// }
|
||||
|
||||
async function submitAddModal() {
|
||||
let result = await addUserToWorkspace(email, router.query.id);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
async function submitAddModal() {
|
||||
let result = await addUserToWorkspace(email, router.query.id);
|
||||
if (result?.invitee && result?.latestKey) {
|
||||
const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY");
|
||||
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
// assymmetrically decrypt symmetric key with local private key
|
||||
const key = decryptAssymmetric({
|
||||
ciphertext: result.latestKey.encryptedKey,
|
||||
nonce: result.latestKey.nonce,
|
||||
publicKey: result.latestKey.sender.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
const { ciphertext, nonce } = encryptAssymmetric({
|
||||
plaintext: key,
|
||||
publicKey: result.invitee.publicKey,
|
||||
privateKey: PRIVATE_KEY,
|
||||
});
|
||||
|
||||
uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce);
|
||||
}
|
||||
setEmail("");
|
||||
setIsAddOpen(false);
|
||||
router.reload();
|
||||
}
|
||||
uploadKeys(router.query.id, result.invitee._id, ciphertext, nonce);
|
||||
}
|
||||
setEmail("");
|
||||
setIsAddOpen(false);
|
||||
router.reload();
|
||||
}
|
||||
|
||||
function openAddModal() {
|
||||
setIsAddOpen(true);
|
||||
}
|
||||
function openAddModal() {
|
||||
setIsAddOpen(true);
|
||||
}
|
||||
|
||||
const [userList, setUserList] = useState();
|
||||
const [orgUserList, setOrgUserList] = useState([]);
|
||||
useEffect(async () => {
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
const [userList, setUserList] = useState();
|
||||
const [orgUserList, setOrgUserList] = useState([]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
useEffect(async () => {
|
||||
const user = await getUser();
|
||||
setPersonalEmail(user.email);
|
||||
|
||||
workspaceId = router.query.id;
|
||||
let workspaceUsers = await getWorkspaceUsers({
|
||||
workspaceId,
|
||||
});
|
||||
const tempUserList = workspaceUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email:
|
||||
user.user?.email == null ? user.inviteEmail : user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey,
|
||||
}));
|
||||
setUserList(tempUserList);
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setOrgUserList(orgUsers);
|
||||
setEmail(
|
||||
orgUsers
|
||||
?.filter((user) => user.status == "accepted")
|
||||
.map((user) => user.user.email)
|
||||
.filter(
|
||||
(email) =>
|
||||
!tempUserList
|
||||
?.map((user1) => user1.email)
|
||||
.includes(email)
|
||||
)[0]
|
||||
);
|
||||
}, []);
|
||||
workspaceId = router.query.id;
|
||||
let workspaceUsers = await getWorkspaceUsers({
|
||||
workspaceId,
|
||||
});
|
||||
const tempUserList = workspaceUsers.map((user) => ({
|
||||
key: guidGenerator(),
|
||||
firstName: user.user?.firstName,
|
||||
lastName: user.user?.lastName,
|
||||
email: user.user?.email == null ? user.inviteEmail : user.user?.email,
|
||||
role: user?.role,
|
||||
status: user?.status,
|
||||
userId: user.user?._id,
|
||||
membershipId: user._id,
|
||||
publicKey: user.user?.publicKey,
|
||||
}));
|
||||
setUserList(tempUserList);
|
||||
const orgUsers = await getOrganizationUsers({
|
||||
orgId: localStorage.getItem("orgData.id"),
|
||||
});
|
||||
setOrgUserList(orgUsers);
|
||||
setEmail(
|
||||
orgUsers
|
||||
?.filter((user) => user.status == "accepted")
|
||||
.map((user) => user.user.email)
|
||||
.filter(
|
||||
(email) => !tempUserList?.map((user1) => user1.email).includes(email)
|
||||
)[0]
|
||||
);
|
||||
}, []);
|
||||
|
||||
return userList ? (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start">
|
||||
<Head>
|
||||
<title>{t("settings:meta.members.head-title")}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<NavHeader
|
||||
pageName={t("settings:meta.members.title")}
|
||||
isProjectRelated={true}
|
||||
/>
|
||||
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl">
|
||||
<p className="font-semibold mr-4 text-white">
|
||||
{t("settings:meta.members.title")}
|
||||
</p>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
{t("settings:meta.members.description")}
|
||||
</p>
|
||||
</div>
|
||||
<AddProjectMemberDialog
|
||||
isOpen={isAddOpen}
|
||||
closeModal={closeAddModal}
|
||||
submitModal={submitAddModal}
|
||||
email={email}
|
||||
data={orgUserList
|
||||
?.filter((user) => user.status == "accepted")
|
||||
.map((user) => user.user.email)
|
||||
.filter(
|
||||
(email) =>
|
||||
!userList
|
||||
?.map((user1) => user1.email)
|
||||
.includes(email)
|
||||
)}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmail}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="px-2 pb-1 w-full flex flex-row items-start max-w-5xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center ml-4">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchUsers}
|
||||
onChange={(e) => setSearchUsers(e.target.value)}
|
||||
placeholder={t("settings:search-members")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4">
|
||||
<Button
|
||||
text={t("settings:add-member")}
|
||||
onButtonPressed={openAddModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-y-auto max-w-5xl mx-6">
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
myUser={personalEmail}
|
||||
filter={searchUsers}
|
||||
resendInvite={submitAddModal}
|
||||
isOrg={false}
|
||||
// onClick={openDeleteModal}
|
||||
// deleteUser={deleteMembership}
|
||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||
className="w-full mx-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="loading animation"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
return userList ? (
|
||||
<div className="bg-bunker-800 md:h-screen flex flex-col justify-start">
|
||||
<Head>
|
||||
<title>{t("settings:meta.members.head-title")}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
<NavHeader
|
||||
pageName={t("settings:meta.members.title")}
|
||||
isProjectRelated={true}
|
||||
/>
|
||||
<div className="flex flex-col justify-start items-start px-6 py-6 pb-4 text-3xl">
|
||||
<p className="font-semibold mr-4 text-white">
|
||||
{t("settings:meta.members.title")}
|
||||
</p>
|
||||
<p className="mr-4 text-base text-gray-400">
|
||||
{t("settings:meta.members.description")}
|
||||
</p>
|
||||
</div>
|
||||
<AddProjectMemberDialog
|
||||
isOpen={isAddOpen}
|
||||
closeModal={closeAddModal}
|
||||
submitModal={submitAddModal}
|
||||
email={email}
|
||||
data={orgUserList
|
||||
?.filter((user) => user.status == "accepted")
|
||||
.map((user) => user.user.email)
|
||||
.filter(
|
||||
(email) => !userList?.map((user1) => user1.email).includes(email)
|
||||
)}
|
||||
workspaceId={workspaceId}
|
||||
setEmail={setEmail}
|
||||
/>
|
||||
{/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */}
|
||||
<div className="px-2 pb-1 w-full flex flex-row items-start max-w-5xl">
|
||||
<div className="h-10 w-full bg-white/5 mt-2 flex items-center rounded-md flex flex-row items-center ml-4">
|
||||
<FontAwesomeIcon
|
||||
className="bg-white/5 rounded-l-md py-3 pl-4 pr-2 text-gray-400"
|
||||
icon={faMagnifyingGlass}
|
||||
/>
|
||||
<input
|
||||
className="pl-2 text-gray-400 rounded-r-md bg-white/5 w-full h-full outline-none"
|
||||
value={searchUsers}
|
||||
onChange={(e) => setSearchUsers(e.target.value)}
|
||||
placeholder={t("settings:search-members")}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 ml-2 min-w-max flex flex-row items-start justify-start mr-4">
|
||||
<Button
|
||||
text={t("settings:add-member")}
|
||||
onButtonPressed={openAddModal}
|
||||
color="mineshaft"
|
||||
size="md"
|
||||
icon={faPlus}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-y-auto max-w-5xl mx-6">
|
||||
<UserTable
|
||||
userData={userList}
|
||||
changeData={setUserList}
|
||||
myUser={personalEmail}
|
||||
filter={searchUsers}
|
||||
resendInvite={submitAddModal}
|
||||
isOrg={false}
|
||||
// onClick={openDeleteModal}
|
||||
// deleteUser={deleteMembership}
|
||||
// setUserIdToBeDeleted={setUserIdToBeDeleted}
|
||||
className="w-full mx-6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="relative z-10 w-10/12 mr-auto h-full ml-2 bg-bunker-800 flex flex-col items-center justify-center">
|
||||
<div className="absolute top-0 bg-bunker h-14 border-b border-mineshaft-700 w-full"></div>
|
||||
<Image
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
width={120}
|
||||
alt="loading animation"
|
||||
></Image>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Users.requireAuth = true;
|
||||
|
||||
Reference in New Issue
Block a user