ARCH - TASKER DEPLOY: закрепление live baseline и env-driven сборки
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
# Setup pnpm package manager with corepack and configure global bin directory for caching
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PNPM_HOME/bin:$PATH"
|
||||
RUN corepack enable
|
||||
|
||||
# *****************************************************************************
|
||||
# STAGE 1: Build the project
|
||||
# *****************************************************************************
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
ARG TURBO_VERSION=2.9.4
|
||||
RUN corepack enable pnpm && pnpm add -g turbo@${TURBO_VERSION}
|
||||
COPY . .
|
||||
|
||||
RUN turbo prune --scope=web --docker
|
||||
|
||||
# *****************************************************************************
|
||||
# STAGE 2: Install dependencies & build the project
|
||||
# *****************************************************************************
|
||||
# Add lockfile and package.json's of isolated subworkspace
|
||||
FROM base AS installer
|
||||
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# First install the dependencies (as they change less often)
|
||||
COPY .gitignore .gitignore
|
||||
COPY --from=builder /app/out/json/ .
|
||||
COPY --from=builder /app/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN corepack enable pnpm
|
||||
|
||||
# Copy full directory structure before fetch to ensure all package.json files are available
|
||||
COPY --from=builder /app/out/full/ .
|
||||
COPY turbo.json turbo.json
|
||||
|
||||
# Fetch dependencies to cache store, then install offline with dev deps
|
||||
RUN pnpm fetch --store-dir=/pnpm/store
|
||||
RUN CI=true pnpm install --offline --frozen-lockfile --store-dir=/pnpm/store
|
||||
|
||||
ARG VITE_API_BASE_URL=""
|
||||
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
|
||||
|
||||
ARG VITE_ADMIN_BASE_URL=""
|
||||
ENV VITE_ADMIN_BASE_URL=$VITE_ADMIN_BASE_URL
|
||||
|
||||
ARG VITE_ADMIN_BASE_PATH="/nodedcsudo"
|
||||
ENV VITE_ADMIN_BASE_PATH=$VITE_ADMIN_BASE_PATH
|
||||
|
||||
ARG VITE_LIVE_BASE_URL=""
|
||||
ENV VITE_LIVE_BASE_URL=$VITE_LIVE_BASE_URL
|
||||
|
||||
ARG VITE_LIVE_BASE_PATH="/live"
|
||||
ENV VITE_LIVE_BASE_PATH=$VITE_LIVE_BASE_PATH
|
||||
|
||||
ARG VITE_SPACE_BASE_URL=""
|
||||
ENV VITE_SPACE_BASE_URL=$VITE_SPACE_BASE_URL
|
||||
|
||||
ARG VITE_SPACE_BASE_PATH="/spaces"
|
||||
ENV VITE_SPACE_BASE_PATH=$VITE_SPACE_BASE_PATH
|
||||
|
||||
ARG VITE_WEB_BASE_URL=""
|
||||
ENV VITE_WEB_BASE_URL=$VITE_WEB_BASE_URL
|
||||
|
||||
ARG VITE_NODEDC_OIDC_LOGIN_ENABLED=""
|
||||
ENV VITE_NODEDC_OIDC_LOGIN_ENABLED=$VITE_NODEDC_OIDC_LOGIN_ENABLED
|
||||
|
||||
ARG VITE_NODEDC_OIDC_LOGIN_URL=""
|
||||
ENV VITE_NODEDC_OIDC_LOGIN_URL=$VITE_NODEDC_OIDC_LOGIN_URL
|
||||
|
||||
ARG VITE_NODEDC_LAUNCHER_URL=""
|
||||
ENV VITE_NODEDC_LAUNCHER_URL=$VITE_NODEDC_LAUNCHER_URL
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV TURBO_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN pnpm turbo run build --filter=web
|
||||
|
||||
# *****************************************************************************
|
||||
# STAGE 3: Serve with nginx
|
||||
# *****************************************************************************
|
||||
FROM nginx:1.27-alpine AS production
|
||||
|
||||
COPY apps/web/nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=installer /app/apps/web/build/client /usr/share/nginx/html
|
||||
RUN chmod -R a+rX /usr/share/nginx/html
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||
CMD curl -fsS http://127.0.0.1:3000/ >/dev/null || exit 1
|
||||
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
+2
-22
@@ -8,11 +8,10 @@
|
||||
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { Shapes } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { cn } from "@plane/utils";
|
||||
// components
|
||||
import { TopNavPowerK } from "@/components/navigation";
|
||||
import { buildNodeDCBrandConfigUrl, buildNodeDCLauncherUrl } from "@/helpers/nodedc-auth";
|
||||
import { useNodeDCBrandLinkUrl } from "@/hooks/use-nodedc-brand-link-url";
|
||||
import { UserMenuRoot } from "@/components/workspace/sidebar/user-menu-root";
|
||||
import { WorkspaceMenuRoot } from "@/components/workspace/sidebar/workspace-menu-root";
|
||||
import { useHome } from "@/hooks/store/use-home";
|
||||
@@ -32,26 +31,7 @@ export const ExpandedProjectShellToolbarLayout = ({
|
||||
}: TProjectShellToolbarLayoutProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { toggleWidgetSettings } = useHome();
|
||||
const [logoLinkUrl, setLogoLinkUrl] = useState(buildNodeDCLauncherUrl);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
fetch(buildNodeDCBrandConfigUrl(), { cache: "no-store" })
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((payload: { logoLinkUrl?: string } | null) => {
|
||||
if (isMounted && payload?.logoLinkUrl) {
|
||||
setLogoLinkUrl(payload.logoLinkUrl);
|
||||
}
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.warn(error instanceof Error ? error.message : "Не удалось загрузить brand config NODE.DC");
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
const logoLinkUrl = useNodeDCBrandLinkUrl();
|
||||
|
||||
return (
|
||||
<div className={cn("nodedc-expanded-toolbar-shell", { "nodedc-home-top-toolbar": isWorkspaceHome })}>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useTranslation } from "@plane/i18n";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
import { PageHead } from "@/components/core/page-title";
|
||||
import { EAuthModes } from "@/helpers/authentication.helper";
|
||||
import { useNodeDCBrandLinkUrl } from "@/hooks/use-nodedc-brand-link-url";
|
||||
import { useInstance } from "@/hooks/store/use-instance";
|
||||
|
||||
const authContentMap = {
|
||||
@@ -68,17 +69,19 @@ type TAuthHeaderBase = {
|
||||
|
||||
export function AuthHeaderBase(props: TAuthHeaderBase) {
|
||||
const { pageTitle, additionalAction } = props;
|
||||
const logoLinkUrl = useNodeDCBrandLinkUrl();
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle + " - NODE.DC"} />
|
||||
<div className="sticky top-0 flex w-full flex-shrink-0 items-center justify-between gap-6 px-2 py-1">
|
||||
<Link href="/">
|
||||
<a href={logoLinkUrl}>
|
||||
<PlaneLockup
|
||||
height={31}
|
||||
width={148}
|
||||
className="nodedc-auth-logo-lockup text-primary transition-opacity hover:opacity-90"
|
||||
/>
|
||||
</Link>
|
||||
</a>
|
||||
{additionalAction}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -12,14 +12,23 @@ import GradientBgLogo from "@/app/assets/auth/gradient-bg-logo.webp?url";
|
||||
import DefaultLayout from "@/layouts/default-layout";
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
import { Button } from "@plane/propel/button";
|
||||
import { useNodeDCBrandLinkUrl } from "@/hooks/use-nodedc-brand-link-url";
|
||||
|
||||
export function InstanceNotReady() {
|
||||
const logoLinkUrl = useNodeDCBrandLinkUrl();
|
||||
|
||||
return (
|
||||
<DefaultLayout>
|
||||
<div className="relative z-10 flex h-screen w-screen overflow-hidden">
|
||||
<div className="flex h-full w-full flex-col items-center px-8 pt-6 pb-10">
|
||||
<div className="sticky top-0 flex w-full shrink-0 items-center justify-between gap-6">
|
||||
<PlaneLockup height={40} width={190} className="nodedc-auth-logo-lockup text-primary" />
|
||||
<a href={logoLinkUrl} aria-label="NODE.DC">
|
||||
<PlaneLockup
|
||||
height={40}
|
||||
width={190}
|
||||
className="nodedc-auth-logo-lockup text-primary transition-opacity hover:opacity-90"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-7">
|
||||
<div className="nodedc-error-shell flex max-w-3xl flex-col items-center gap-11 text-center">
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
*/
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { InboxIcon } from "@plane/propel/icons";
|
||||
import { Tooltip } from "@plane/propel/tooltip";
|
||||
import { buildNodeDCBrandConfigUrl, buildNodeDCLauncherUrl } from "@/helpers/nodedc-auth";
|
||||
import { useNodeDCBrandLinkUrl } from "@/hooks/use-nodedc-brand-link-url";
|
||||
import { UserMenuRoot } from "@/components/workspace/sidebar/user-menu-root";
|
||||
|
||||
type TNodeDCStandaloneShellProps = {
|
||||
@@ -24,25 +23,7 @@ type TNodeDCStandaloneShellProps = {
|
||||
export const NodeDCStandaloneShell = (props: TNodeDCStandaloneShellProps) => {
|
||||
const { children, notificationsCount = 0, onOpenNotifications, showUserControls = false } = props;
|
||||
const { t } = useTranslation();
|
||||
const [logoLinkUrl, setLogoLinkUrl] = useState(buildNodeDCLauncherUrl);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
fetch(buildNodeDCBrandConfigUrl(), { cache: "no-store" })
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((payload: { logoLinkUrl?: string } | null) => {
|
||||
if (isMounted && payload?.logoLinkUrl) setLogoLinkUrl(payload.logoLinkUrl);
|
||||
return undefined;
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.warn(error instanceof Error ? error.message : "Не удалось загрузить brand config NODE.DC");
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
const logoLinkUrl = useNodeDCBrandLinkUrl();
|
||||
|
||||
return (
|
||||
<div className="relative flex min-h-screen w-full overflow-hidden bg-[#050507] text-primary">
|
||||
|
||||
@@ -8,12 +8,14 @@ import { observer } from "mobx-react";
|
||||
// plane imports
|
||||
import { PlaneLockup } from "@plane/propel/icons";
|
||||
// hooks
|
||||
import { useNodeDCBrandLinkUrl } from "@/hooks/use-nodedc-brand-link-url";
|
||||
import { useUser } from "@/hooks/store/user";
|
||||
// local imports
|
||||
import { SwitchAccountDropdown } from "./switch-account-dropdown";
|
||||
|
||||
export const OnboardingHeader = observer(function OnboardingHeader() {
|
||||
const { data: user } = useUser();
|
||||
const logoLinkUrl = useNodeDCBrandLinkUrl();
|
||||
|
||||
const userName = user?.display_name
|
||||
? user.display_name
|
||||
@@ -27,7 +29,9 @@ export const OnboardingHeader = observer(function OnboardingHeader() {
|
||||
<div className="h-full w-full bg-accent-primary" />
|
||||
</div>
|
||||
<div className="flex w-full items-center justify-between gap-6 px-6">
|
||||
<PlaneLockup height={20} width={95} className="text-primary" />
|
||||
<a href={logoLinkUrl} aria-label="NODE.DC">
|
||||
<PlaneLockup height={20} width={95} className="text-primary transition-opacity hover:opacity-90" />
|
||||
</a>
|
||||
<SwitchAccountDropdown fullName={userName} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { buildNodeDCBrandConfigUrl, buildNodeDCLauncherUrl } from "@/helpers/nodedc-auth";
|
||||
|
||||
type TNodeDCBrandPayload = {
|
||||
logoLinkUrl?: string | null;
|
||||
};
|
||||
|
||||
export function useNodeDCBrandLinkUrl() {
|
||||
const [logoLinkUrl, setLogoLinkUrl] = useState(buildNodeDCLauncherUrl);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
|
||||
fetch(buildNodeDCBrandConfigUrl(), { cache: "no-store" })
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.then((payload: TNodeDCBrandPayload | null) => {
|
||||
const configuredUrl = typeof payload?.logoLinkUrl === "string" ? payload.logoLinkUrl.trim() : "";
|
||||
if (isMounted && configuredUrl) setLogoLinkUrl(configuredUrl);
|
||||
return undefined;
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
console.warn(error instanceof Error ? error.message : "Не удалось загрузить brand config NODE.DC");
|
||||
});
|
||||
|
||||
return () => {
|
||||
isMounted = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return logoLinkUrl;
|
||||
}
|
||||
Reference in New Issue
Block a user