Merge branch 'feat/self_hosted_instance' of github.com:makeplane/plane into feat/self_hosted_instance

This commit is contained in:
pablohashescobar 2023-11-15 19:25:04 +05:30
commit 1e9f4dd938
8 changed files with 185 additions and 26 deletions

View File

@ -12,8 +12,9 @@ from sentry_sdk import capture_exception
# Module imports
from .base import BaseAPIView
from plane.license.models import Instance
from plane.license.utils.instance_value import get_configuration_value
from plane.license.models import Instance, InstanceConfiguration
from plane.license.utils.instance_value import get_configuration_value
class ConfigurationEndpoint(BaseAPIView):
permission_classes = [
@ -21,27 +22,75 @@ class ConfigurationEndpoint(BaseAPIView):
]
def get(self, request):
instance_configuration = Instance.objects.values("key", "value")
instance_configuration = InstanceConfiguration.objects.values("key", "value")
data = {}
# Authentication
data["google_client_id"] = get_configuration_value(instance_configuration, "GOOGLE_CLIENT_ID")
data["github_client_id"] = get_configuration_value(instance_configuration,"GITHUB_CLIENT_ID")
data["github_app_name"] = get_configuration_value(instance_configuration, "GITHUB_APP_NAME")
data["google_client_id"] = get_configuration_value(
instance_configuration,
"GOOGLE_CLIENT_ID",
os.environ.get("GOOGLE_CLIENT_ID", None),
)
data["github_client_id"] = get_configuration_value(
instance_configuration,
"GITHUB_CLIENT_ID",
os.environ.get("GITHUB_CLIENT_ID", None),
)
data["github_app_name"] = get_configuration_value(
instance_configuration,
"GITHUB_APP_NAME",
os.environ.get("GITHUB_APP_NAME", None),
)
data["magic_login"] = (
bool(get_configuration_value(instance_configuration, "EMAIL_HOST_USER")) and bool(get_configuration_value(instance_configuration, "EMAIL_HOST_PASSWORD"))
) and get_configuration_value(instance_configuration, "ENABLE_MAGIC_LINK_LOGIN", "0") == "1"
bool(
get_configuration_value(
instance_configuration,
"EMAIL_HOST_USER",
os.environ.get("GITHUB_APP_NAME", None),
),
)
and bool(
get_configuration_value(
instance_configuration,
"EMAIL_HOST_PASSWORD",
os.environ.get("GITHUB_APP_NAME", None),
)
)
) and get_configuration_value(
instance_configuration, "ENABLE_MAGIC_LINK_LOGIN", "0"
) == "1"
data["email_password_login"] = (
get_configuration_value(instance_configuration, "ENABLE_EMAIL_PASSWORD", "0") == "1"
get_configuration_value(
instance_configuration, "ENABLE_EMAIL_PASSWORD", "0"
)
== "1"
)
# Slack client
data["slack_client_id"] = get_configuration_value(instance_configuration, "SLACK_CLIENT_ID")
data["slack_client_id"] = get_configuration_value(
instance_configuration,
"SLACK_CLIENT_ID",
os.environ.get("SLACK_CLIENT_ID", None),
)
# Posthog
data["posthog_api_key"] = get_configuration_value(instance_configuration, "POSTHOG_API_KEY")
data["posthog_host"] = get_configuration_value(instance_configuration, "POSTHOG_HOST")
data["posthog_api_key"] = get_configuration_value(
instance_configuration,
"POSTHOG_API_KEY",
os.environ.get("POSTHOG_API_KEY", None),
)
data["posthog_host"] = get_configuration_value(
instance_configuration,
"POSTHOG_HOST",
os.environ.get("POSTHOG_HOST", None),
)
# Unsplash
data["has_unsplash_configured"] = bool(get_configuration_value(instance_configuration, "UNSPLASH_ACCESS_KEY"))
data["has_unsplash_configured"] = bool(
get_configuration_value(
instance_configuration,
"UNSPLASH_ACCESS_KEY",
os.environ.get("UNSPLASH_ACCESS_KEY", None),
)
)
return Response(data, status=status.HTTP_200_OK)

View File

@ -19,7 +19,6 @@ from plane.license.utils.instance_value import get_configuration_value
@shared_task
def workspace_invitation(email, workspace_id, token, current_site, invitor):
try:
user = User.objects.get(email=invitor)
workspace = Workspace.objects.get(pk=workspace_id)
@ -28,9 +27,7 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
)
# Relative link
relative_link = (
f"/workspace-invitations/?invitation_id={workspace_member_invite.id}&email={email}&slug={workspace.slug}"
)
relative_link = f"/workspace-invitations/?invitation_id={workspace_member_invite.id}&email={email}&slug={workspace.slug}"
# The complete url including the domain
abs_url = current_site + relative_link
@ -57,17 +54,33 @@ def workspace_invitation(email, workspace_id, token, current_site, invitor):
workspace_member_invite.message = text_content
workspace_member_invite.save()
instance_configuration = InstanceConfiguration.objects.filter(key__startswith='EMAIL_').values("key", "value")
instance_configuration = InstanceConfiguration.objects.filter(
key__startswith="EMAIL_"
).values("key", "value")
connection = get_connection(
host=get_configuration_value(instance_configuration, "EMAIL_HOST"),
port=int(get_configuration_value(instance_configuration, "EMAIL_PORT", "587")),
port=int(
get_configuration_value(instance_configuration, "EMAIL_PORT", "587")
),
username=get_configuration_value(instance_configuration, "EMAIL_HOST_USER"),
password=get_configuration_value(instance_configuration, "EMAIL_HOST_PASSWORD"),
use_tls=bool(get_configuration_value(instance_configuration, "EMAIL_USE_TLS", "1")),
use_ssl=bool(get_configuration_value(instance_configuration, "EMAIL_USE_SSL", "0")),
password=get_configuration_value(
instance_configuration, "EMAIL_HOST_PASSWORD"
),
use_tls=bool(
get_configuration_value(instance_configuration, "EMAIL_USE_TLS", "1")
),
use_ssl=bool(
get_configuration_value(instance_configuration, "EMAIL_USE_SSL", "0")
),
)
# Initiate email alternatives
msg = EmailMultiAlternatives(subject=subject, body=text_content, from_email=get_configuration_value(instance_configuration, "EMAIL_FROM"), to=[email], connection=connection)
msg = EmailMultiAlternatives(
subject=subject,
body=text_content,
from_email=get_configuration_value(instance_configuration, "EMAIL_FROM"),
to=[email],
connection=connection,
)
msg.attach_alternative(html_content, "text/html")
msg.send()

View File

@ -5,6 +5,7 @@ import ssl
import certifi
from datetime import timedelta
from urllib.parse import urlparse
# Django imports
from django.core.management.utils import get_random_secret_key
@ -236,7 +237,6 @@ if AWS_S3_ENDPOINT_URL:
AWS_S3_URL_PROTOCOL = f"{parsed_url.scheme}:"
# JWT Auth Configuration
SIMPLE_JWT = {
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=10080),
@ -332,4 +332,3 @@ SCOUT_NAME = "Plane"
# Set the variable true if running in docker environment
DOCKERIZED = int(os.environ.get("DOCKERIZED", 1)) == 1
USE_MINIO = int(os.environ.get("USE_MINIO", 0)) == 1

View File

@ -0,0 +1,21 @@
import { FC } from "react";
// ui
import { Breadcrumbs } from "@plane/ui";
// icons
import { Settings } from "lucide-react";
export const InstanceAdminHeader: FC = () => (
<div className="relative flex w-full flex-shrink-0 flex-row z-10 h-[3.75rem] items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
<div className="flex items-center gap-2 flex-grow w-full whitespace-nowrap overflow-ellipsis">
<div>
<Breadcrumbs>
<Breadcrumbs.BreadcrumbItem
type="text"
icon={<Settings className="h-4 w-4 text-custom-text-300" />}
label="General"
/>
</Breadcrumbs>
</div>
</div>
</div>
);

View File

@ -0,0 +1,3 @@
export * from "./layout";
export * from "./sidebar";
export * from "./header";

View File

@ -0,0 +1,32 @@
import { FC, ReactNode } from "react";
// layouts
import { UserAuthWrapper } from "layouts/auth-layout";
// components
import { InstanceAdminSidebar } from "./sidebar";
import { InstanceAdminHeader } from "./header";
export interface IInstanceAdminLayout {
children: ReactNode;
}
export const InstanceAdminLayout: FC<IInstanceAdminLayout> = (props) => {
const { children } = props;
return (
<>
<UserAuthWrapper>
<div className="relative flex h-screen w-full overflow-hidden">
<InstanceAdminSidebar />
<main className="relative flex flex-col h-full w-full overflow-hidden bg-custom-background-100">
<InstanceAdminHeader />
<div className="h-full w-full overflow-hidden">
<div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">
<>{children}</>
</div>
</div>
</main>
</div>
</UserAuthWrapper>
</>
);
};

View File

@ -0,0 +1,26 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// components
import { WorkspaceHelpSection } from "components/workspace";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
export interface IAppSidebar {}
export const InstanceAdminSidebar: FC<IAppSidebar> = observer(() => {
// store
const { theme: themStore } = useMobxStore();
return (
<div
id="app-sidebar"
className={`fixed md:relative inset-y-0 flex flex-col bg-custom-sidebar-background-100 h-full flex-shrink-0 flex-grow-0 border-r border-custom-sidebar-border-200 z-20 duration-300 ${
themStore?.sidebarCollapsed ? "" : "md:w-[280px]"
} ${themStore?.sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`}
>
<div className="flex h-full w-full flex-1 flex-col">
<WorkspaceHelpSection />
</div>
</div>
);
});

16
web/pages/admin/index.tsx Normal file
View File

@ -0,0 +1,16 @@
import { ReactElement } from "react";
// layouts
import { InstanceAdminLayout } from "layouts/admin-layout";
// types
import { NextPageWithLayout } from "types/app";
const InstanceAdminPage: NextPageWithLayout = () => {
console.log("admin page");
return <div>Admin Page</div>;
};
InstanceAdminPage.getLayout = function getLayout(page: ReactElement) {
return <InstanceAdminLayout>{page}</InstanceAdminLayout>;
};
export default InstanceAdminPage;