Add setup page
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,26 @@
|
||||
To finishing the setup, you need to do the following steps:
|
||||
|
||||
## API Config
|
||||
The API Config file are located at `src/config/api.ts`. You need to fill in the following information:
|
||||
```js
|
||||
module.exports = {
|
||||
client_id: "lorem",
|
||||
client_secret: "ipsum",
|
||||
refresh_token: "dolor",
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Config
|
||||
The environment variables are differ for each hosting service. If you are using Vercel, you can add the environment variables on the project settings page.
|
||||
```dotenv
|
||||
ENCRYPTION_KEY="Encryption"
|
||||
```
|
||||
|
||||
**NOTE:** Make sure to redeploy the site after adding the environment variables.
|
||||
|
||||
## Customizing the Site
|
||||
The site can be customized by editing the `src/config/site.ts` file. You can change the site title, description, and other information.
|
||||
All the explanation also included in the file.
|
||||
|
||||
|
||||
And that's it, you're done with the setup. You can now start using the site.
|
||||
@@ -0,0 +1,47 @@
|
||||
# Google Cloud Setup
|
||||
|
||||
Table of Contents:
|
||||
- [Making a Google Cloud Project and enabling Google Drive API](#making-a-google-cloud-project-and-enabling-google-drive-api)
|
||||
- [Creating Oauth Consent Screen](#creating-oauth-consent-screen)
|
||||
- [Creating Oauth Client ID](#creating-oauth-client-id)
|
||||
- [Getting Refresh Token](#getting-refresh-token)
|
||||
|
||||
## Making a Google Cloud Project and enabling Google Drive API
|
||||
1. Open [Google Cloud Console](https://console.cloud.google.com/).
|
||||
2. Click on the project selector dropdown in the top left corner and select or create a new project.
|
||||

|
||||
3. Activate the [Google Drive API](https://console.cloud.google.com/apis/library/drive.googleapis.com) for your project.
|
||||

|
||||
4. After activating the API, you will be redirected to the API overview page. For creating Oauth Client ID, you need to create Oauth consent first, you can do this by clicking on the "Oauth Consent Screen" menu.
|
||||
|
||||
## Creating Oauth Consent Screen
|
||||
1. On the Oauth consent screen page, select "External" as the user type and click on the "Create" button.
|
||||

|
||||
2. On the next page, fill in the required information and click on the "Save" button.
|
||||
(In my case I'm only filling `App name`, `User support email`, and `Developer contact information`.)
|
||||
3. On the **Scopes** Page, select the following scopes and click on the "Save" button, then click on the "Continue" button.
|
||||

|
||||
4. On the **Test Users** Page, click on the "Add Users" button and add your email address or any other email address that you want to use. Then click on the "Save and Continue" button.
|
||||
5. And you're done with the Oauth consent screen. Now you can create the Oauth Client ID by clicking on the "Credentials" menu.
|
||||
**NOTE: We don't need to publish the app, since we're only using it for our own purpose.**
|
||||
|
||||
## Creating Oauth Client ID
|
||||
1. On the **Credentials** page, click on the "Create Credentials" button and select "Oauth Client ID".
|
||||

|
||||
2. On the next page, select "Web Application" as the application type, and fill in the required information.
|
||||
3. In the **Authorized redirect URIs** section, add the following redirect URI: `https://developers.google.com/oauthplayground`, then click on the "Create" button.
|
||||
4. After creating the Oauth Client ID, you will be redirected to the **Credentials** page. Then copy the Client ID, and Client Secret. (You will need them later on the bottom of this page, and to fetch refresh token.)
|
||||
|
||||
## Getting Refresh Token
|
||||
1. Open [Google OAuth Playground](https://developers.google.com/oauthplayground/).
|
||||
2. Click on the gear icon on the upper right corner and check the box next to "Use your own OAuth credentials" and paste the Client ID and Client Secret that you copied earlier.
|
||||

|
||||
3. Select the following scopes and click on the "Authorize APIs" button.
|
||||

|
||||
4. Google will ask you to log in to your Google account, you need to log in using the account that specified on Test User (Creating Oauth Consent Screen - step 4).
|
||||
5. It will show you a warning that the app is not verified, simply click on "Continue" button, and it will redirect you back to the Playground.
|
||||

|
||||
6. Click on the "Exchange authorization code for tokens" button, and it will show you the refresh token. Copy the refresh token and paste it on the bottom of this page.
|
||||

|
||||
7. You can close the Playground tab now.
|
||||
8. You're done with the Google Cloud Setup.
|
||||
@@ -0,0 +1,112 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import Link from "next/link";
|
||||
import useSWR from "swr";
|
||||
import fetcher from "@utils/swrFetch";
|
||||
import LoadingFeedback from "@components/APIFeedback/Loading";
|
||||
import MarkdownRender from "@components/utility/MarkdownRender";
|
||||
import useLocalStorage from "@hooks/useLocalStorage";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { MdContentCopy } from "react-icons/md";
|
||||
import useCopyText from "@hooks/useCopyText";
|
||||
|
||||
type ConfigProps = {
|
||||
client_id: string;
|
||||
client_secret: string;
|
||||
refresh_token: string;
|
||||
};
|
||||
const defaultConfig: ConfigProps = {
|
||||
client_id: "",
|
||||
client_secret: "",
|
||||
refresh_token: "",
|
||||
};
|
||||
|
||||
export default function SetupFinal() {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [tempKey] = useLocalStorage("tempEncryption", "");
|
||||
const [tempConfig] = useLocalStorage<ConfigProps>(
|
||||
"tempGoogleCloud",
|
||||
defaultConfig,
|
||||
);
|
||||
const [dataConfig, setDataConfig] = useState<ConfigProps>(defaultConfig);
|
||||
const [dataKey, setDataKey] = useState<string>("");
|
||||
const [mdContent, setMdContent] = useState<string>("");
|
||||
|
||||
const dataRef = useRef<HTMLElement>(null);
|
||||
const copyText = useCopyText();
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
if (tempConfig) {
|
||||
setDataConfig(tempConfig);
|
||||
}
|
||||
if (tempKey) {
|
||||
setDataKey(tempKey);
|
||||
}
|
||||
setIsLoading(false);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setIsLoading(true);
|
||||
const mdContent = `To finishing the setup, you need to do the following steps:
|
||||
|
||||
## API Config
|
||||
The API Config file are located at \`src/config/api.ts\`. You need to fill in the following information:
|
||||
\`\`\`js
|
||||
module.exports = {
|
||||
client_id: "${dataConfig.client_id}",
|
||||
client_secret: "${dataConfig.client_secret}",
|
||||
refresh_token: "${dataConfig.refresh_token}",
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
## Environment Config
|
||||
The environment variables are differ for each hosting service. If you are using Vercel, you can add the environment variables on the project settings page.
|
||||
\`\`\`
|
||||
ENCRYPTION_KEY="${dataKey}"
|
||||
\`\`\`
|
||||
|
||||
**NOTE:** Make sure to redeploy the site after adding the environment variables.
|
||||
|
||||
## Customizing the Site
|
||||
The site can be customized by editing the \`src/config/site.ts\` file. You can change the site title, description, and other information.
|
||||
All the explanation also included in the file.`;
|
||||
|
||||
setMdContent(mdContent);
|
||||
setIsLoading(false);
|
||||
|
||||
// Remove the temp data
|
||||
localStorage.removeItem("tempEncryption");
|
||||
localStorage.removeItem("tempGoogleCloud");
|
||||
}, [dataConfig, dataKey]);
|
||||
|
||||
return (
|
||||
<div className={"mx-auto flex max-w-screen-xl flex-col gap-4"}>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Finishing setup</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
{isLoading ? (
|
||||
<LoadingFeedback message={"Loading data..."} />
|
||||
) : (
|
||||
<MarkdownRender content={mdContent} />
|
||||
)}
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full flex-row items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/"}>
|
||||
<button className={"primary"}>Finish</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { MdContentCopy, MdWarning } from "react-icons/md";
|
||||
import { ReactNode, useEffect, useState } from "react";
|
||||
import { encrypt, generateRandomEncryptionKey } from "@utils/encryptionHelper";
|
||||
import useCopyText from "@hooks/useCopyText";
|
||||
import { toast } from "react-toastify";
|
||||
import Link from "next/link";
|
||||
import useLocalStorage from "@hooks/useLocalStorage";
|
||||
import MarkdownRender from "@components/utility/MarkdownRender";
|
||||
import useSWR from "swr";
|
||||
import fetcher from "@utils/swrFetch";
|
||||
import LoadingFeedback from "@components/APIFeedback/Loading";
|
||||
|
||||
type StepProps = {
|
||||
stepName: string;
|
||||
imgSrc?: string;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function SetupGoogleCloud() {
|
||||
const [encryptionKey] = useLocalStorage("tempEncryption", "");
|
||||
const [settingJson, setSettingJson] = useLocalStorage("tempGoogleCloud", {
|
||||
client_id: "",
|
||||
client_secret: "",
|
||||
refresh_token: "",
|
||||
});
|
||||
const [client_id, setClientID] = useState<string>("");
|
||||
const [client_secret, setClientSecret] = useState<string>("");
|
||||
const [refresh_token, setRefreshToken] = useState<string>("");
|
||||
const [allowNext, setAllowNext] = useState<boolean>(false);
|
||||
|
||||
const { data, isLoading } = useSWR("/setup/GoogleCloudStep.md", fetcher);
|
||||
|
||||
useEffect(() => {
|
||||
if (client_id && client_secret && refresh_token) {
|
||||
setAllowNext(true);
|
||||
} else {
|
||||
setAllowNext(false);
|
||||
}
|
||||
}, [client_id, client_secret, refresh_token]);
|
||||
|
||||
return (
|
||||
<div className='mx-auto flex max-w-screen-xl flex-col gap-4'>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Setting up Google Cloud</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div className={"banner"}>
|
||||
<span>
|
||||
If you already have Client ID, Client Secret, and Refresh Token.
|
||||
Click <a href={"#skip"}>here</a> to skip this step.
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
|
||||
>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
{isLoading ? (
|
||||
<LoadingFeedback message={"Loading file..."} />
|
||||
) : (
|
||||
<MarkdownRender content={data as string} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={"card"}
|
||||
id={"skip"}
|
||||
>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Store Google Cloud credentials</span>
|
||||
</div>
|
||||
<div className={"divider-horizontal"} />
|
||||
<div
|
||||
className={"mx-auto flex w-full max-w-screen-md flex-col gap-4 px-4"}
|
||||
>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Client ID</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={client_id}
|
||||
onChange={(e) => setClientID(e.target.value)}
|
||||
placeholder={"Enter your client id here..."}
|
||||
/>
|
||||
</div>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Client Secret</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={client_secret}
|
||||
onChange={(e) => setClientSecret(e.target.value)}
|
||||
placeholder={"Enter your client secret here..."}
|
||||
/>
|
||||
<span className={"text-xs tablet:text-sm"}>
|
||||
* Client secret will be encrypted using your encryption key
|
||||
</span>
|
||||
</div>
|
||||
<div className={"flex flex-col gap-2"}>
|
||||
<span className='font-bold'>Refresh Token</span>
|
||||
<input
|
||||
type={"text"}
|
||||
className={"pr-4"}
|
||||
value={refresh_token}
|
||||
onChange={(e) => setRefreshToken(e.target.value)}
|
||||
placeholder={"Enter your refresh token here..."}
|
||||
/>
|
||||
<span className={"text-xs tablet:text-sm"}>
|
||||
* Refresh token will be encrypted using your encryption key
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/setup/encryption"}>
|
||||
<button className={"secondary"}>Previous Page</button>
|
||||
</Link>
|
||||
<Link
|
||||
href={allowNext ? "/setup/final" : ""}
|
||||
onClick={() => {
|
||||
if (!allowNext) return;
|
||||
setSettingJson({
|
||||
client_id,
|
||||
client_secret: encrypt(client_secret, encryptionKey),
|
||||
refresh_token: encrypt(refresh_token, encryptionKey),
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className={"primary"}
|
||||
disabled={!allowNext}
|
||||
>
|
||||
Next Page
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { GetServerSideProps } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Setup() {
|
||||
return (
|
||||
<div className={"mx-auto flex max-w-screen-xl flex-col gap-4"}>
|
||||
<div className={"card"}>
|
||||
<div className='flex w-full items-center justify-between rounded-lg px-4'>
|
||||
<span className='font-bold'>Starting configuration</span>
|
||||
</div>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<p className={"mx-auto max-w-screen-md px-4 py-4 text-center"}>
|
||||
This page will guide you through the initial configuration for
|
||||
deploying guDora-index. It start from setting up your encryption key,
|
||||
Google cloud, and setting up the project.
|
||||
<br />
|
||||
<br />
|
||||
This step will take you couple of minutes. So please read the
|
||||
instructions and follow them carefully.
|
||||
</p>
|
||||
|
||||
<div className={"divider-horizontal"} />
|
||||
|
||||
<div
|
||||
className={
|
||||
"mx-auto flex w-full flex-row items-center justify-end gap-2 tablet:gap-4"
|
||||
}
|
||||
>
|
||||
<Link href={"/setup/encryption"}>
|
||||
<button className={"primary"}>Start</button>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -71,11 +71,16 @@ html, body {
|
||||
}
|
||||
a {
|
||||
@apply hover:opacity-75 transition duration-150;
|
||||
@apply text-blue-500 hover:text-blue-600 dark:text-blue-400 dark:hover:text-blue-500 underline;
|
||||
}
|
||||
a:has(div.items), a:has(span) {
|
||||
@apply text-zinc-900 dark:text-zinc-100 hover:text-zinc-800 dark:hover:text-zinc-200;
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
|
||||
input, textarea, button, select {
|
||||
@apply rounded-full px-4 py-2 tablet:py-1;
|
||||
@apply rounded-lg px-4 py-2 tablet:py-1;
|
||||
@apply text-sm tablet:text-base;
|
||||
@apply outline-none border;
|
||||
@apply border-zinc-400 hover:border-blue-300 focus:border-blue-400;
|
||||
@@ -91,20 +96,33 @@ html, body {
|
||||
button {
|
||||
@apply cursor-pointer disabled:cursor-default;
|
||||
}
|
||||
button[disabled] {
|
||||
@apply cursor-not-allowed !important;
|
||||
@apply border-zinc-300 dark:border-zinc-700 !important;
|
||||
@apply bg-zinc-400 dark:bg-zinc-800 !important;
|
||||
@apply text-zinc-500 !important;
|
||||
}
|
||||
button.primary {
|
||||
@apply text-zinc-100 dark:text-zinc-300;
|
||||
@apply text-zinc-100 dark:text-zinc-100;
|
||||
@apply border-blue-400 hover:border-blue-500 active:border-blue-600;
|
||||
@apply bg-blue-500 hover:bg-blue-600 active:bg-blue-400;
|
||||
@apply bg-blue-500 hover:bg-blue-700 active:bg-blue-400;
|
||||
@apply dark:border-blue-500 dark:hover:border-blue-600 dark:active:border-blue-700;
|
||||
@apply dark:bg-blue-500 dark:hover:bg-blue-400 dark:active:bg-blue-600;
|
||||
@apply dark:bg-blue-500 dark:hover:bg-blue-600 dark:active:bg-blue-600;
|
||||
}
|
||||
button.secondary{
|
||||
@apply text-zinc-100 dark:text-zinc-300;
|
||||
@apply text-zinc-100 dark:text-zinc-100;
|
||||
@apply border-zinc-400 hover:border-zinc-500 active:border-zinc-600;
|
||||
@apply bg-zinc-500 hover:bg-zinc-600 active:bg-zinc-400;
|
||||
@apply dark:border-zinc-500 dark:hover:border-zinc-600 dark:active:border-zinc-700;
|
||||
@apply dark:bg-zinc-500 dark:hover:bg-zinc-400 dark:active:bg-zinc-600;
|
||||
}
|
||||
button.danger {
|
||||
@apply text-zinc-100 dark:text-zinc-100;
|
||||
@apply border-red-400 hover:border-red-500 active:border-red-600;
|
||||
@apply bg-red-500 hover:bg-red-700 active:bg-red-400;
|
||||
@apply dark:border-red-500 dark:hover:border-red-600 dark:active:border-red-700;
|
||||
@apply dark:bg-red-500 dark:hover:bg-red-600 dark:active:bg-red-600;
|
||||
}
|
||||
|
||||
table {
|
||||
@apply w-full border-collapse;
|
||||
@@ -118,6 +136,10 @@ html, body {
|
||||
td {
|
||||
@apply border-b border-zinc-400 dark:border-zinc-700 py-1.5;
|
||||
}
|
||||
|
||||
img.thumbnail {
|
||||
@apply rounded-lg w-full max-w-screen-tablet mt-2 mb-4;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@@ -128,6 +150,18 @@ html, body {
|
||||
@apply bg-zinc-100 dark:bg-zinc-900;
|
||||
}
|
||||
|
||||
div.banner {
|
||||
@apply rounded-lg p-2 tablet:p-4 my-2 tablet:my-4;
|
||||
@apply border border-zinc-400 dark:border-zinc-700;
|
||||
@apply drop-shadow;
|
||||
@apply bg-zinc-100 dark:bg-zinc-900;
|
||||
@apply flex gap-4 items-center;
|
||||
}
|
||||
div.banner.warning {
|
||||
@apply border-yellow-400 dark:border-yellow-600;
|
||||
@apply bg-yellow-100 dark:bg-yellow-900;
|
||||
}
|
||||
|
||||
div.layoutDropdown {
|
||||
@apply rounded-lg pr-2 pl-4 py-1 cursor-pointer;
|
||||
@apply flex items-center justify-between gap-8;
|
||||
@@ -187,7 +221,7 @@ html, body {
|
||||
}
|
||||
|
||||
.navbar a {
|
||||
@apply text-inherit font-normal not-italic;
|
||||
@apply text-inherit font-normal not-italic no-underline;
|
||||
}
|
||||
|
||||
.interactive {
|
||||
|
||||
@@ -54,7 +54,7 @@ html.dark {
|
||||
}
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
pre[class*="language-"], pre {
|
||||
background: var(--syntax-bg);
|
||||
color: var(--syntax-fg);
|
||||
text-shadow: 0 1px rgba(0, 0, 0, 0);
|
||||
@@ -92,7 +92,7 @@ pre[class*="language-"] *::selection {
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
pre[class*="language-"], pre {
|
||||
padding: 1em;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
|
||||