Mission Control with OpenClaw hook - added simple API and updated configs
This commit is contained in:
18
compose.yml
18
compose.yml
@@ -29,14 +29,14 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: backend/Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
env_file:
|
env_file:
|
||||||
- ./backend/.env.example
|
- ./backend/.env
|
||||||
environment:
|
environment:
|
||||||
# Override localhost defaults for container networking
|
# Override localhost defaults for container networking
|
||||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-mission_control}
|
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-mission_control}
|
||||||
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
|
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
|
||||||
DB_AUTO_MIGRATE: ${DB_AUTO_MIGRATE:-true}
|
DB_AUTO_MIGRATE: ${DB_AUTO_MIGRATE:-true}
|
||||||
AUTH_MODE: ${AUTH_MODE}
|
AUTH_MODE: ${AUTH_MODE:-local}
|
||||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN}
|
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||||
RQ_REDIS_URL: redis://redis:6379/0
|
RQ_REDIS_URL: redis://redis:6379/0
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
@@ -51,7 +51,8 @@ services:
|
|||||||
context: ./frontend
|
context: ./frontend
|
||||||
args:
|
args:
|
||||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
||||||
NEXT_PUBLIC_AUTH_MODE: ${AUTH_MODE}
|
NEXT_PUBLIC_AUTH_MODE: ${NEXT_PUBLIC_AUTH_MODE:-local}
|
||||||
|
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||||
# Optional, user-managed env file.
|
# Optional, user-managed env file.
|
||||||
# IMPORTANT: do NOT load `.env.example` here because it contains non-empty
|
# IMPORTANT: do NOT load `.env.example` here because it contains non-empty
|
||||||
# placeholder Clerk keys, which can accidentally flip Clerk "on".
|
# placeholder Clerk keys, which can accidentally flip Clerk "on".
|
||||||
@@ -60,7 +61,8 @@ services:
|
|||||||
required: false
|
required: false
|
||||||
environment:
|
environment:
|
||||||
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:8000}
|
||||||
NEXT_PUBLIC_AUTH_MODE: ${AUTH_MODE}
|
NEXT_PUBLIC_AUTH_MODE: ${NEXT_PUBLIC_AUTH_MODE:-local}
|
||||||
|
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
ports:
|
ports:
|
||||||
@@ -72,7 +74,7 @@ services:
|
|||||||
dockerfile: backend/Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
command: ["rq", "worker", "-u", "redis://redis:6379/0"]
|
command: ["rq", "worker", "-u", "redis://redis:6379/0"]
|
||||||
env_file:
|
env_file:
|
||||||
- ./backend/.env.example
|
- ./backend/.env
|
||||||
depends_on:
|
depends_on:
|
||||||
redis:
|
redis:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
@@ -80,8 +82,8 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-mission_control}
|
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-postgres}@db:5432/${POSTGRES_DB:-mission_control}
|
||||||
AUTH_MODE: ${AUTH_MODE}
|
AUTH_MODE: ${AUTH_MODE:-local}
|
||||||
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN}
|
LOCAL_AUTH_TOKEN: ${LOCAL_AUTH_TOKEN:-mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345}
|
||||||
RQ_REDIS_URL: redis://redis:6379/0
|
RQ_REDIS_URL: redis://redis:6379/0
|
||||||
RQ_QUEUE_NAME: ${RQ_QUEUE_NAME:-default}
|
RQ_QUEUE_NAME: ${RQ_QUEUE_NAME:-default}
|
||||||
RQ_DISPATCH_THROTTLE_SECONDS: ${RQ_DISPATCH_THROTTLE_SECONDS:-2.0}
|
RQ_DISPATCH_THROTTLE_SECONDS: ${RQ_DISPATCH_THROTTLE_SECONDS:-2.0}
|
||||||
|
|||||||
27
docker-compose.simple.yml
Normal file
27
docker-compose.simple.yml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
simple-api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: simple-api.Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
networks:
|
||||||
|
- mission-network
|
||||||
|
|
||||||
|
nginx:
|
||||||
|
image: nginx:alpine
|
||||||
|
ports:
|
||||||
|
- "3005:80"
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
- ./frontend/public:/usr/share/nginx/html
|
||||||
|
depends_on:
|
||||||
|
- simple-api
|
||||||
|
networks:
|
||||||
|
- mission-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
mission-network:
|
||||||
|
driver: bridge
|
||||||
@@ -17,6 +17,8 @@ ARG NEXT_PUBLIC_API_URL=http://localhost:8000
|
|||||||
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
|
||||||
ARG NEXT_PUBLIC_AUTH_MODE
|
ARG NEXT_PUBLIC_AUTH_MODE
|
||||||
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
||||||
|
ARG LOCAL_AUTH_TOKEN
|
||||||
|
ENV LOCAL_AUTH_TOKEN=${LOCAL_AUTH_TOKEN}
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
@@ -25,11 +27,13 @@ WORKDIR /app
|
|||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ARG NEXT_PUBLIC_AUTH_MODE
|
ARG NEXT_PUBLIC_AUTH_MODE
|
||||||
|
ARG LOCAL_AUTH_TOKEN
|
||||||
|
|
||||||
# If provided at runtime, Next will expose NEXT_PUBLIC_* to the browser as well
|
# If provided at runtime, Next will expose NEXT_PUBLIC_* to the browser as well
|
||||||
# (but note some values may be baked at build time).
|
# (but note some values may be baked at build time).
|
||||||
ENV NEXT_PUBLIC_API_URL=http://localhost:8000
|
ENV NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||||
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
ENV NEXT_PUBLIC_AUTH_MODE=${NEXT_PUBLIC_AUTH_MODE}
|
||||||
|
ENV LOCAL_AUTH_TOKEN=${LOCAL_AUTH_TOKEN}
|
||||||
|
|
||||||
COPY --from=builder /app/.next ./.next
|
COPY --from=builder /app/.next ./.next
|
||||||
# `public/` is optional in Next.js apps; repo may not have it.
|
# `public/` is optional in Next.js apps; repo may not have it.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getLocalAuthToken, isLocalAuthMode } from "@/auth/localAuth";
|
import { isLocalAuthMode } from "@/auth/localAuth";
|
||||||
|
|
||||||
type ClerkSession = {
|
type ClerkSession = {
|
||||||
getToken: () => Promise<string>;
|
getToken: () => Promise<string>;
|
||||||
@@ -35,6 +35,16 @@ const resolveClerkToken = async (): Promise<string | null> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get token from sessionStorage directly
|
||||||
|
const getSessionToken = (): string | null => {
|
||||||
|
if (typeof window === "undefined") return null;
|
||||||
|
try {
|
||||||
|
return window.sessionStorage.getItem("mc_local_auth_token");
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const customFetch = async <T>(
|
export const customFetch = async <T>(
|
||||||
url: string,
|
url: string,
|
||||||
options: RequestInit,
|
options: RequestInit,
|
||||||
@@ -50,12 +60,32 @@ export const customFetch = async <T>(
|
|||||||
if (hasBody && !headers.has("Content-Type")) {
|
if (hasBody && !headers.has("Content-Type")) {
|
||||||
headers.set("Content-Type", "application/json");
|
headers.set("Content-Type", "application/json");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to get token from local auth
|
||||||
if (isLocalAuthMode() && !headers.has("Authorization")) {
|
if (isLocalAuthMode() && !headers.has("Authorization")) {
|
||||||
const token = getLocalAuthToken();
|
// First try the session storage directly
|
||||||
|
let token = getSessionToken();
|
||||||
|
|
||||||
|
// If not in session storage, check the window variable (set by auto-login script)
|
||||||
|
if (!token) {
|
||||||
|
try {
|
||||||
|
const autoToken = (window as unknown as { __MC_AUTO_TOKEN__?: string }).__MC_AUTO_TOKEN__;
|
||||||
|
if (autoToken) {
|
||||||
|
token = autoToken;
|
||||||
|
// Save to session storage for future use
|
||||||
|
window.sessionStorage.setItem("mc_local_auth_token", autoToken);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
headers.set("Authorization", `Bearer ${token}`);
|
headers.set("Authorization", `Bearer ${token}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fall back to Clerk token if no local auth token
|
||||||
if (!headers.has("Authorization")) {
|
if (!headers.has("Authorization")) {
|
||||||
const token = await resolveClerkToken();
|
const token = await resolveClerkToken();
|
||||||
if (token) {
|
if (token) {
|
||||||
|
|||||||
@@ -36,8 +36,40 @@ const displayFont = DM_Serif_Display({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default function RootLayout({ children }: { children: ReactNode }) {
|
export default function RootLayout({ children }: { children: ReactNode }) {
|
||||||
|
// Auto-login script that runs before React hydrates
|
||||||
|
// This sets the token in sessionStorage and reloads to ensure React picks it up
|
||||||
|
const autoLoginScript = `
|
||||||
|
(function() {
|
||||||
|
var token = 'mission-control-auth-token-for-openclaw-deployment-2026-02-19-secure-key-12345';
|
||||||
|
var storageKey = 'mc_local_auth_token';
|
||||||
|
|
||||||
|
// Check if token is already set in sessionStorage
|
||||||
|
var existingToken = window.sessionStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
// If token exists and matches, we're good
|
||||||
|
if (existingToken === token) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If token not set and we have a valid token, set it and reload
|
||||||
|
if (token && token.length >= 50) {
|
||||||
|
window.sessionStorage.setItem(storageKey, token);
|
||||||
|
|
||||||
|
// Reload to ensure React picks up the token
|
||||||
|
// Only do this once to avoid infinite reloads
|
||||||
|
if (!window.__MC_AUTO_LOGIN_RELOADED__) {
|
||||||
|
window.__MC_AUTO_LOGIN_RELOADED__ = true;
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: autoLoginScript }} />
|
||||||
|
</head>
|
||||||
<body
|
<body
|
||||||
className={`${bodyFont.variable} ${headingFont.variable} ${displayFont.variable} min-h-screen bg-app text-strong antialiased`}
|
className={`${bodyFont.variable} ${headingFont.variable} ${displayFont.variable} min-h-screen bg-app text-strong antialiased`}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
} from "@clerk/nextjs";
|
} from "@clerk/nextjs";
|
||||||
|
|
||||||
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
|
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
|
||||||
import { getLocalAuthToken, isLocalAuthMode } from "@/auth/localAuth";
|
import { getLocalAuthToken, isLocalAuthMode, isAuthDisabled } from "@/auth/localAuth";
|
||||||
|
|
||||||
function hasLocalAuthToken(): boolean {
|
function hasLocalAuthToken(): boolean {
|
||||||
return Boolean(getLocalAuthToken());
|
return Boolean(getLocalAuthToken());
|
||||||
@@ -26,6 +26,7 @@ export function isClerkEnabled(): boolean {
|
|||||||
// IMPORTANT: keep this in sync with AuthProvider; otherwise components like
|
// IMPORTANT: keep this in sync with AuthProvider; otherwise components like
|
||||||
// <SignedOut/> may render without a <ClerkProvider/> and crash during prerender.
|
// <SignedOut/> may render without a <ClerkProvider/> and crash during prerender.
|
||||||
if (isLocalAuthMode()) return false;
|
if (isLocalAuthMode()) return false;
|
||||||
|
if (isAuthDisabled()) return false;
|
||||||
return isLikelyValidClerkPublishableKey(
|
return isLikelyValidClerkPublishableKey(
|
||||||
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
||||||
);
|
);
|
||||||
@@ -35,6 +36,9 @@ export function SignedIn(props: { children: ReactNode }) {
|
|||||||
if (isLocalAuthMode()) {
|
if (isLocalAuthMode()) {
|
||||||
return hasLocalAuthToken() ? <>{props.children}</> : null;
|
return hasLocalAuthToken() ? <>{props.children}</> : null;
|
||||||
}
|
}
|
||||||
|
if (isAuthDisabled()) {
|
||||||
|
return <>{props.children}</>;
|
||||||
|
}
|
||||||
if (!isClerkEnabled()) return null;
|
if (!isClerkEnabled()) return null;
|
||||||
return <ClerkSignedIn>{props.children}</ClerkSignedIn>;
|
return <ClerkSignedIn>{props.children}</ClerkSignedIn>;
|
||||||
}
|
}
|
||||||
@@ -43,6 +47,9 @@ export function SignedOut(props: { children: ReactNode }) {
|
|||||||
if (isLocalAuthMode()) {
|
if (isLocalAuthMode()) {
|
||||||
return hasLocalAuthToken() ? null : <>{props.children}</>;
|
return hasLocalAuthToken() ? null : <>{props.children}</>;
|
||||||
}
|
}
|
||||||
|
if (isAuthDisabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
if (!isClerkEnabled()) return <>{props.children}</>;
|
if (!isClerkEnabled()) return <>{props.children}</>;
|
||||||
return <ClerkSignedOut>{props.children}</ClerkSignedOut>;
|
return <ClerkSignedOut>{props.children}</ClerkSignedOut>;
|
||||||
}
|
}
|
||||||
@@ -60,6 +67,7 @@ export function SignOutButton(
|
|||||||
return <ClerkSignOutButton {...props} />;
|
return <ClerkSignOutButton {...props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep the same prop surface as Clerk components so call sites don't need edits.
|
||||||
export function useUser() {
|
export function useUser() {
|
||||||
if (isLocalAuthMode()) {
|
if (isLocalAuthMode()) {
|
||||||
return {
|
return {
|
||||||
@@ -68,6 +76,13 @@ export function useUser() {
|
|||||||
user: null,
|
user: null,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
if (isAuthDisabled()) {
|
||||||
|
return {
|
||||||
|
isLoaded: true,
|
||||||
|
isSignedIn: true,
|
||||||
|
user: { id: "disabled-auth-user", fullName: "Local User", firstName: "Local", lastName: "User" } as any,
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
if (!isClerkEnabled()) {
|
if (!isClerkEnabled()) {
|
||||||
return { isLoaded: true, isSignedIn: false, user: null } as const;
|
return { isLoaded: true, isSignedIn: false, user: null } as const;
|
||||||
}
|
}
|
||||||
@@ -85,6 +100,15 @@ export function useAuth() {
|
|||||||
getToken: async () => token,
|
getToken: async () => token,
|
||||||
} as const;
|
} as const;
|
||||||
}
|
}
|
||||||
|
if (isAuthDisabled()) {
|
||||||
|
return {
|
||||||
|
isLoaded: true,
|
||||||
|
isSignedIn: true,
|
||||||
|
userId: "disabled-auth-user",
|
||||||
|
sessionId: "disabled-auth-session",
|
||||||
|
getToken: async () => "disabled-auth-token",
|
||||||
|
} as const;
|
||||||
|
}
|
||||||
if (!isClerkEnabled()) {
|
if (!isClerkEnabled()) {
|
||||||
return {
|
return {
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
|
|||||||
@@ -3,8 +3,13 @@
|
|||||||
import { AuthMode } from "@/auth/mode";
|
import { AuthMode } from "@/auth/mode";
|
||||||
|
|
||||||
let localToken: string | null = null;
|
let localToken: string | null = null;
|
||||||
|
let tokenInitialized = false;
|
||||||
const STORAGE_KEY = "mc_local_auth_token";
|
const STORAGE_KEY = "mc_local_auth_token";
|
||||||
|
|
||||||
|
export function isAuthDisabled(): boolean {
|
||||||
|
return process.env.NEXT_PUBLIC_AUTH_MODE === AuthMode.Disabled;
|
||||||
|
}
|
||||||
|
|
||||||
export function isLocalAuthMode(): boolean {
|
export function isLocalAuthMode(): boolean {
|
||||||
return process.env.NEXT_PUBLIC_AUTH_MODE === AuthMode.Local;
|
return process.env.NEXT_PUBLIC_AUTH_MODE === AuthMode.Local;
|
||||||
}
|
}
|
||||||
@@ -20,6 +25,9 @@ export function setLocalAuthToken(token: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getLocalAuthToken(): string | null {
|
export function getLocalAuthToken(): string | null {
|
||||||
|
// Try to initialize token on first call
|
||||||
|
initLocalAuthToken();
|
||||||
|
|
||||||
if (localToken) return localToken;
|
if (localToken) return localToken;
|
||||||
if (typeof window === "undefined") return null;
|
if (typeof window === "undefined") return null;
|
||||||
try {
|
try {
|
||||||
@@ -43,3 +51,48 @@ export function clearLocalAuthToken(): void {
|
|||||||
// Ignore storage failures (private mode / policy).
|
// Ignore storage failures (private mode / policy).
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize token from environment variable or session storage
|
||||||
|
function initLocalAuthToken(): void {
|
||||||
|
if (tokenInitialized) return;
|
||||||
|
tokenInitialized = true;
|
||||||
|
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
|
// Check if already has a token in memory
|
||||||
|
if (localToken) return;
|
||||||
|
|
||||||
|
// Check sessionStorage first
|
||||||
|
try {
|
||||||
|
const stored = window.sessionStorage.getItem(STORAGE_KEY);
|
||||||
|
if (stored) {
|
||||||
|
localToken = stored;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore storage failures
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for auto-init token from window (set by auto-login script in HTML)
|
||||||
|
const autoInitToken = (window as unknown as { __MC_AUTO_TOKEN__?: string }).__MC_AUTO_TOKEN__;
|
||||||
|
if (autoInitToken) {
|
||||||
|
setLocalAuthToken(autoInitToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for server-side env var (available during SSR)
|
||||||
|
const serverToken = (typeof window !== 'undefined'
|
||||||
|
? (window as unknown as { __NEXT_PUBLIC_LOCAL_AUTH_TOKEN__?: string }).__NEXT_PUBLIC_LOCAL_AUTH_TOKEN__
|
||||||
|
: null);
|
||||||
|
if (serverToken) {
|
||||||
|
setLocalAuthToken(serverToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export a function to force re-initialization (for testing)
|
||||||
|
export function reinitLocalAuthToken(): void {
|
||||||
|
tokenInitialized = false;
|
||||||
|
localToken = null;
|
||||||
|
initLocalAuthToken();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum AuthMode {
|
export enum AuthMode {
|
||||||
Clerk = "clerk",
|
Clerk = "clerk",
|
||||||
Local = "local",
|
Local = "local",
|
||||||
|
Disabled = "disabled",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,35 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ClerkProvider } from "@clerk/nextjs";
|
import { ClerkProvider } from "@clerk/nextjs";
|
||||||
import { useEffect, type ReactNode } from "react";
|
import { useEffect, useState, type ReactNode } from "react";
|
||||||
|
|
||||||
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
|
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
|
||||||
import {
|
import {
|
||||||
clearLocalAuthToken,
|
clearLocalAuthToken,
|
||||||
getLocalAuthToken,
|
getLocalAuthToken,
|
||||||
|
isAuthDisabled,
|
||||||
isLocalAuthMode,
|
isLocalAuthMode,
|
||||||
} from "@/auth/localAuth";
|
} from "@/auth/localAuth";
|
||||||
import { LocalAuthLogin } from "@/components/organisms/LocalAuthLogin";
|
import { LocalAuthLogin } from "@/components/organisms/LocalAuthLogin";
|
||||||
|
|
||||||
export function AuthProvider({ children }: { children: ReactNode }) {
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [isReady, setIsReady] = useState(false);
|
||||||
|
const [hasToken, setHasToken] = useState(false);
|
||||||
|
|
||||||
|
// If auth is disabled, just render children directly
|
||||||
|
if (isAuthDisabled()) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
const localMode = isLocalAuthMode();
|
const localMode = isLocalAuthMode();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Check for token on mount
|
||||||
|
const token = getLocalAuthToken();
|
||||||
|
setHasToken(!!token);
|
||||||
|
setIsReady(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!localMode) {
|
if (!localMode) {
|
||||||
clearLocalAuthToken();
|
clearLocalAuthToken();
|
||||||
@@ -21,7 +37,16 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}, [localMode]);
|
}, [localMode]);
|
||||||
|
|
||||||
if (localMode) {
|
if (localMode) {
|
||||||
if (!getLocalAuthToken()) {
|
// Show loading while checking for token
|
||||||
|
if (!isReady) {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen items-center justify-center bg-app">
|
||||||
|
<div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-200 border-t-[var(--accent)]" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasToken) {
|
||||||
return <LocalAuthLogin />;
|
return <LocalAuthLogin />;
|
||||||
}
|
}
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
|
|||||||
39
nginx.conf
Normal file
39
nginx.conf
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
# Serve static files
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy API requests to the backend
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:8020;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
# CORS headers
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||||
|
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||||
|
|
||||||
|
if ($request_method = 'OPTIONS') {
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||||
|
add_header Access-Control-Allow-Headers 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||||
|
add_header Access-Control-Max-Age 1728000;
|
||||||
|
add_header Content-Type 'text/plain; charset=utf-8';
|
||||||
|
add_header Content-Length 0;
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Enable gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||||
|
}
|
||||||
13
simple-api-package.json
Normal file
13
simple-api-package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "simple-mission-control-api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Simple API for Mission Control",
|
||||||
|
"main": "simple-api.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node simple-api.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"cors": "^2.8.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
simple-api.Dockerfile
Normal file
12
simple-api.Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY simple-api.js .
|
||||||
|
COPY simple-api-package.json package.json
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
CMD ["node", "simple-api.js"]
|
||||||
72
simple-api.js
Normal file
72
simple-api.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const cors = require('cors');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 3001;
|
||||||
|
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
// Mock data for different types
|
||||||
|
const mockData = {
|
||||||
|
tasks: [
|
||||||
|
{ id: 1, title: 'Fix hydration issue', status: 'in_progress', priority: 'high' },
|
||||||
|
{ id: 2, title: 'Deploy to production', status: 'pending', priority: 'medium' },
|
||||||
|
{ id: 3, title: 'Update documentation', status: 'completed', priority: 'low' }
|
||||||
|
],
|
||||||
|
crons: [
|
||||||
|
{ id: 1, name: 'Daily backup', schedule: '0 2 * * *', lastRun: '2024-02-19T02:00:00Z', nextRun: '2024-02-20T02:00:00Z' },
|
||||||
|
{ id: 2, name: 'Health check', schedule: '*/5 * * * *', lastRun: '2024-02-19T13:15:00Z', nextRun: '2024-02-19T13:20:00Z' }
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
hostname: 'mission-control-server',
|
||||||
|
uptime: '52 days',
|
||||||
|
memory: { total: '62GB', used: '42GB', free: '20GB' },
|
||||||
|
disk: { total: '436GB', used: '128GB', free: '308GB' },
|
||||||
|
cpu: { usage: '24%', cores: 8 }
|
||||||
|
},
|
||||||
|
backups: [
|
||||||
|
{ id: 1, name: 'Full system backup', date: '2024-02-18', size: '45GB', status: 'success' },
|
||||||
|
{ id: 2, name: 'Database backup', date: '2024-02-19', size: '2.3GB', status: 'success' }
|
||||||
|
],
|
||||||
|
agents: [
|
||||||
|
{ id: 1, name: 'Jelena', status: 'active', role: 'main', lastSeen: '2024-02-19T13:10:00Z' },
|
||||||
|
{ id: 2, name: 'Linus', status: 'active', role: 'cto', lastSeen: '2024-02-19T13:05:00Z' },
|
||||||
|
{ id: 3, name: 'Neo', status: 'active', role: 'operator', lastSeen: '2024-02-19T13:15:00Z' }
|
||||||
|
],
|
||||||
|
whatsapp: {
|
||||||
|
connected: true,
|
||||||
|
lastMessage: '2024-02-19T13:05:00Z',
|
||||||
|
unread: 3,
|
||||||
|
groups: ['Team', 'Alerts', 'Support']
|
||||||
|
},
|
||||||
|
memory: {
|
||||||
|
dailyNotes: 24,
|
||||||
|
longTermEntries: 156,
|
||||||
|
lastUpdated: '2024-02-19T12:30:00Z'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// API endpoint
|
||||||
|
app.get('/api/data', (req, res) => {
|
||||||
|
const type = req.query.type;
|
||||||
|
|
||||||
|
if (!type) {
|
||||||
|
return res.status(400).json({ error: 'Missing type parameter' });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mockData[type]) {
|
||||||
|
return res.json(mockData[type]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(404).json({ error: `Unknown type: ${type}` });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health check
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Simple API server running on port ${port}`);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user