forked from github/plane
Merge branch 'feat/self_hosted_instance' of github.com:makeplane/plane into feat/self_hosted_instance
This commit is contained in:
commit
1e9f4dd938
@ -12,36 +12,85 @@ from sentry_sdk import capture_exception
|
||||
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
from plane.license.models import Instance
|
||||
from plane.license.models import Instance, InstanceConfiguration
|
||||
from plane.license.utils.instance_value import get_configuration_value
|
||||
|
||||
|
||||
class ConfigurationEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
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)
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
21
web/layouts/admin-layout/header.tsx
Normal file
21
web/layouts/admin-layout/header.tsx
Normal 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>
|
||||
);
|
3
web/layouts/admin-layout/index.ts
Normal file
3
web/layouts/admin-layout/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from "./layout";
|
||||
export * from "./sidebar";
|
||||
export * from "./header";
|
32
web/layouts/admin-layout/layout.tsx
Normal file
32
web/layouts/admin-layout/layout.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
26
web/layouts/admin-layout/sidebar.tsx
Normal file
26
web/layouts/admin-layout/sidebar.tsx
Normal 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
16
web/pages/admin/index.tsx
Normal 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;
|
Loading…
Reference in New Issue
Block a user