feat: complete rewrite of UI/UX for streamlining and pruning
Browse files- backend/clean_all.py +0 -27
- backend/fix_qdrant.py +0 -17
- backend/fix_qdrant2.py +0 -18
- backend/fix_qdrant_yoe.py +0 -17
- backend/test_db.py +0 -8
- backend/wipe.py +0 -19
- backend/wipe_api.py +0 -22
- frontend/src/app/jds/[id]/candidates/[cid]/page.tsx +4 -4
- frontend/src/app/jds/[id]/page.tsx +2 -2
- frontend/src/app/page.tsx +1 -1
- frontend/src/app/pipeline/page.tsx +2 -2
- frontend/src/app/sessions/[id]/page.tsx +4 -4
- frontend/src/app/sessions/page.tsx +5 -6
- supervisord.conf +16 -8
backend/clean_all.py
DELETED
|
@@ -1,27 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
from src.database import engine
|
| 3 |
-
from sqlalchemy import text
|
| 4 |
-
from src.config import get_settings
|
| 5 |
-
from qdrant_client import QdrantClient
|
| 6 |
-
from qdrant_client.models import Distance, VectorParams
|
| 7 |
-
|
| 8 |
-
async def main():
|
| 9 |
-
async with engine.begin() as conn:
|
| 10 |
-
print('Wiping Postgres...')
|
| 11 |
-
await conn.execute(text('DROP SCHEMA public CASCADE'))
|
| 12 |
-
await conn.execute(text('CREATE SCHEMA public'))
|
| 13 |
-
print('Postgres wiped.')
|
| 14 |
-
|
| 15 |
-
import qdrant_client
|
| 16 |
-
settings = get_settings()
|
| 17 |
-
try:
|
| 18 |
-
q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
|
| 19 |
-
q.delete_collection(settings.collection_name)
|
| 20 |
-
q.create_collection(settings.collection_name, vectors_config=VectorParams(size=384, distance=Distance.COSINE))
|
| 21 |
-
print('Qdrant wiped and recreated.')
|
| 22 |
-
except Exception as e:
|
| 23 |
-
print('Qdrant error:', e)
|
| 24 |
-
|
| 25 |
-
if __name__ == '__main__':
|
| 26 |
-
asyncio.run(main())
|
| 27 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/fix_qdrant.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
from qdrant_client import QdrantClient
|
| 3 |
-
from src.config import get_settings
|
| 4 |
-
|
| 5 |
-
settings = get_settings()
|
| 6 |
-
q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
|
| 7 |
-
|
| 8 |
-
try:
|
| 9 |
-
q.create_payload_index(
|
| 10 |
-
collection_name=settings.collection_name,
|
| 11 |
-
field_name='session_id',
|
| 12 |
-
field_schema='keyword'
|
| 13 |
-
)
|
| 14 |
-
print('Payload index created successfully!')
|
| 15 |
-
except Exception as e:
|
| 16 |
-
print('Error creating payload index:', e)
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/fix_qdrant2.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
from qdrant_client import QdrantClient
|
| 3 |
-
from qdrant_client.models import PayloadSchemaType
|
| 4 |
-
from src.config import get_settings
|
| 5 |
-
|
| 6 |
-
settings = get_settings()
|
| 7 |
-
q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
|
| 8 |
-
|
| 9 |
-
try:
|
| 10 |
-
q.create_payload_index(
|
| 11 |
-
collection_name=settings.collection_name,
|
| 12 |
-
field_name='years_of_experience',
|
| 13 |
-
field_schema=PayloadSchemaType.FLOAT
|
| 14 |
-
)
|
| 15 |
-
print('Payload index for years_of_experience created successfully!')
|
| 16 |
-
except Exception as e:
|
| 17 |
-
print('Error creating payload index:', e)
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/fix_qdrant_yoe.py
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
from qdrant_client import QdrantClient
|
| 3 |
-
from src.config import get_settings
|
| 4 |
-
|
| 5 |
-
settings = get_settings()
|
| 6 |
-
q = QdrantClient(url=settings.qdrant_url, api_key=settings.qdrant_api_key)
|
| 7 |
-
|
| 8 |
-
try:
|
| 9 |
-
q.create_payload_index(
|
| 10 |
-
collection_name=settings.collection_name,
|
| 11 |
-
field_name='years_of_experience',
|
| 12 |
-
field_schema='integer'
|
| 13 |
-
)
|
| 14 |
-
print('Years of Experience index created successfully!')
|
| 15 |
-
except Exception as e:
|
| 16 |
-
print('Error creating YOE index:', e)
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/test_db.py
DELETED
|
@@ -1,8 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
from src.database import engine
|
| 3 |
-
|
| 4 |
-
async def test():
|
| 5 |
-
async with engine.begin() as conn:
|
| 6 |
-
await conn.run_sync(lambda *args: print('DB Connection OK'))
|
| 7 |
-
|
| 8 |
-
asyncio.run(test())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/wipe.py
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
import asyncio
|
| 2 |
-
import os
|
| 3 |
-
from sqlalchemy.ext.asyncio import create_async_engine
|
| 4 |
-
from sqlalchemy import text
|
| 5 |
-
from dotenv import load_dotenv
|
| 6 |
-
|
| 7 |
-
load_dotenv()
|
| 8 |
-
url = os.getenv('DATABASE_URL').replace('postgresql:', 'postgresql+asyncpg:').replace('?sslmode=require', '')
|
| 9 |
-
if '?sslmode=require' not in url and 'ep-patient-brook' in url: pass # handle if needed
|
| 10 |
-
|
| 11 |
-
async def wipe():
|
| 12 |
-
engine = create_async_engine(url)
|
| 13 |
-
async with engine.begin() as conn:
|
| 14 |
-
await conn.execute(text('DROP SCHEMA public CASCADE; CREATE SCHEMA public;'))
|
| 15 |
-
print('DB wiped')
|
| 16 |
-
|
| 17 |
-
if __name__ == '__main__':
|
| 18 |
-
asyncio.run(wipe())
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/wipe_api.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
from fastapi import FastAPI
|
| 2 |
-
from sqlalchemy.ext.asyncio import create_async_engine
|
| 3 |
-
from sqlalchemy import text
|
| 4 |
-
from dotenv import load_dotenv
|
| 5 |
-
import asyncio
|
| 6 |
-
import os
|
| 7 |
-
|
| 8 |
-
load_dotenv()
|
| 9 |
-
url = os.getenv('DATABASE_URL').replace('postgresql:', 'postgresql+asyncpg:').replace('?sslmode=require&channel_binding=require', '')
|
| 10 |
-
if '?sslmode=' not in url: url += '?ssl=require'
|
| 11 |
-
|
| 12 |
-
engine = create_async_engine(url)
|
| 13 |
-
|
| 14 |
-
async def main():
|
| 15 |
-
async with engine.begin() as conn:
|
| 16 |
-
print('Wiping...')
|
| 17 |
-
await conn.execute(text('TRUNCATE table job_descriptions, sessions, candidates, match_results CASCADE;'))
|
| 18 |
-
print('Wiped tables!')
|
| 19 |
-
|
| 20 |
-
if __name__ == '__main__':
|
| 21 |
-
asyncio.run(main())
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/src/app/jds/[id]/candidates/[cid]/page.tsx
CHANGED
|
@@ -59,7 +59,7 @@ interface WorkExp {
|
|
| 59 |
export default function CandidateDetailPage() {
|
| 60 |
const params = useParams<{ id: string; cid: string }>();
|
| 61 |
const searchParams = useSearchParams();
|
| 62 |
-
const sessionId = searchParams.get("
|
| 63 |
const { id: jdId, cid: candidateId } = params;
|
| 64 |
|
| 65 |
const [detail, setDetail] = useState<CandidateDetail | null>(null);
|
|
@@ -118,9 +118,9 @@ export default function CandidateDetailPage() {
|
|
| 118 |
|
| 119 |
return (
|
| 120 |
<div className="max-w-7xl mx-auto px-6 py-10">
|
| 121 |
-
<Link href={sessionId ? `/sessions/${sessionId}?jd_id=${jdId}` : `/
|
| 122 |
-
className="text-xs text-[var(--color-dimmer)] hover:text-[var(--color-text)] font-semibold mb-6 inline-block transition-colors">
|
| 123 |
-
← Back to
|
| 124 |
</Link>
|
| 125 |
|
| 126 |
<div className="grid grid-cols-[1fr_300px] gap-6 items-start">
|
|
|
|
| 59 |
export default function CandidateDetailPage() {
|
| 60 |
const params = useParams<{ id: string; cid: string }>();
|
| 61 |
const searchParams = useSearchParams();
|
| 62 |
+
const sessionId = searchParams.get("batch") || undefined;
|
| 63 |
const { id: jdId, cid: candidateId } = params;
|
| 64 |
|
| 65 |
const [detail, setDetail] = useState<CandidateDetail | null>(null);
|
|
|
|
| 118 |
|
| 119 |
return (
|
| 120 |
<div className="max-w-7xl mx-auto px-6 py-10">
|
| 121 |
+
<Link href={sessionId ? `/sessions/${sessionId}?jd_id=${jdId}` : `/sessions`}
|
| 122 |
+
className="text-xs text-[var(--color-dimmer)] hover:text-[var(--color-text)] font-semibold mb-6 inline-block transition-colors uppercase tracking-wider">
|
| 123 |
+
← Back to Candidate Batch
|
| 124 |
</Link>
|
| 125 |
|
| 126 |
<div className="grid grid-cols-[1fr_300px] gap-6 items-start">
|
frontend/src/app/jds/[id]/page.tsx
CHANGED
|
@@ -151,7 +151,7 @@ export default function JDDetailPage() {
|
|
| 151 |
|
| 152 |
<div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-5 mb-8 flex gap-3 items-end flex-wrap">
|
| 153 |
<div className="flex-1 min-w-[200px]">
|
| 154 |
-
<label className="text-xs text-[var(--color-muted)] mb-1.5 block">
|
| 155 |
<select
|
| 156 |
id="session-select"
|
| 157 |
className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-2.5 text-sm outline-none focus:border-[var(--color-brand)] transition-all text-[var(--color-text)]"
|
|
@@ -200,7 +200,7 @@ export default function JDDetailPage() {
|
|
| 200 |
{(match.results || []).map((c) => (
|
| 201 |
<Link
|
| 202 |
key={c.candidate_id}
|
| 203 |
-
href={`/jds/${jdId}/candidates/${c.candidate_id}${selectedSession ? `?
|
| 204 |
className="flex items-center gap-4 p-4 bg-[var(--color-card)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand)] hover:shadow-[0_0_20px_var(--color-brand-dim)] transition-all group"
|
| 205 |
>
|
| 206 |
<div className={`w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0 text-white ${c.rank <= 3 ? "bg-gradient-to-br from-amber-400 to-amber-600" : "bg-[var(--color-surface-2)] !text-[var(--color-muted)]"}`}>
|
|
|
|
| 151 |
|
| 152 |
<div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-5 mb-8 flex gap-3 items-end flex-wrap">
|
| 153 |
<div className="flex-1 min-w-[200px]">
|
| 154 |
+
<label className="text-xs text-[var(--color-muted)] mb-1.5 block uppercase font-bold tracking-wider">Select Candidate Batch</label>
|
| 155 |
<select
|
| 156 |
id="session-select"
|
| 157 |
className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-2.5 text-sm outline-none focus:border-[var(--color-brand)] transition-all text-[var(--color-text)]"
|
|
|
|
| 200 |
{(match.results || []).map((c) => (
|
| 201 |
<Link
|
| 202 |
key={c.candidate_id}
|
| 203 |
+
href={`/jds/${jdId}/candidates/${c.candidate_id}${selectedSession ? `?batch=${selectedSession}` : ""}`}
|
| 204 |
className="flex items-center gap-4 p-4 bg-[var(--color-card)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand)] hover:shadow-[0_0_20px_var(--color-brand-dim)] transition-all group"
|
| 205 |
>
|
| 206 |
<div className={`w-9 h-9 rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0 text-white ${c.rank <= 3 ? "bg-gradient-to-br from-amber-400 to-amber-600" : "bg-[var(--color-surface-2)] !text-[var(--color-muted)]"}`}>
|
frontend/src/app/page.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import { redirect } from 'next/navigation';
|
| 2 |
|
| 3 |
export default function Home() {
|
| 4 |
-
redirect('/
|
| 5 |
}
|
|
|
|
| 1 |
import { redirect } from 'next/navigation';
|
| 2 |
|
| 3 |
export default function Home() {
|
| 4 |
+
redirect('/sessions');
|
| 5 |
}
|
frontend/src/app/pipeline/page.tsx
CHANGED
|
@@ -128,7 +128,7 @@ export default function PipelinePage() {
|
|
| 128 |
|
| 129 |
try {
|
| 130 |
// 1. Create Session & JDs parallel
|
| 131 |
-
const sessionPromise = api.createSession(sessionName, "
|
| 132 |
const jdPromises = jds.map(jd => api.createJD(jd.title, jd.desc));
|
| 133 |
|
| 134 |
const [session, ...createdJDs] = await Promise.all([sessionPromise, ...jdPromises]);
|
|
@@ -266,7 +266,7 @@ export default function PipelinePage() {
|
|
| 266 |
{state.status === "idle" ? (
|
| 267 |
<div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-8 shadow-xl shadow-black/5">
|
| 268 |
<div className="mb-6">
|
| 269 |
-
<label className="block text-xs font-
|
| 270 |
<input type="text" placeholder="e.g. Q3 Engineering Batch (100k)"
|
| 271 |
className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-3 text-sm outline-none focus:border-[var(--color-brand)] transition-all"
|
| 272 |
value={sessionName} onChange={e => setSessionName(e.target.value)} />
|
|
|
|
| 128 |
|
| 129 |
try {
|
| 130 |
// 1. Create Session & JDs parallel
|
| 131 |
+
const sessionPromise = api.createSession(sessionName, "Automated Candidate Batch Ingestion");
|
| 132 |
const jdPromises = jds.map(jd => api.createJD(jd.title, jd.desc));
|
| 133 |
|
| 134 |
const [session, ...createdJDs] = await Promise.all([sessionPromise, ...jdPromises]);
|
|
|
|
| 266 |
{state.status === "idle" ? (
|
| 267 |
<div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-8 shadow-xl shadow-black/5">
|
| 268 |
<div className="mb-6">
|
| 269 |
+
<label className="block text-xs font-bold text-[var(--color-muted)] mb-2 uppercase tracking-wider">Candidate Batch Name</label>
|
| 270 |
<input type="text" placeholder="e.g. Q3 Engineering Batch (100k)"
|
| 271 |
className="w-full bg-[var(--color-surface-2)] border border-[var(--color-border-strong)] rounded-xl px-4 py-3 text-sm outline-none focus:border-[var(--color-brand)] transition-all"
|
| 272 |
value={sessionName} onChange={e => setSessionName(e.target.value)} />
|
frontend/src/app/sessions/[id]/page.tsx
CHANGED
|
@@ -178,9 +178,9 @@ export default function SessionDetailPage({ params }: { params: Promise<{ id: st
|
|
| 178 |
{/* HEADER */}
|
| 179 |
<div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-6 mb-6 flex justify-between items-center shadow-sm">
|
| 180 |
<div>
|
| 181 |
-
<Link href="/
|
| 182 |
-
<h1 className="text-3xl font-bold tracking-tight">
|
| 183 |
-
<p className="text-sm text-[var(--color-muted)] mt-1">{session.description || "
|
| 184 |
</div>
|
| 185 |
<div className="text-right">
|
| 186 |
<div className="text-3xl font-bold text-[var(--color-brand-light)]">{session.candidate_count.toLocaleString()}</div>
|
|
@@ -289,7 +289,7 @@ export default function SessionDetailPage({ params }: { params: Promise<{ id: st
|
|
| 289 |
{match.results.map((c) => (
|
| 290 |
<Link
|
| 291 |
key={c.candidate_id}
|
| 292 |
-
href={`/jds/${selectedJD}/candidates/${c.candidate_id}?
|
| 293 |
className="flex items-center justify-between p-4 bg-[var(--color-surface-2)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand-light)] hover:bg-[var(--color-card)] hover:shadow-lg transition-all group"
|
| 294 |
>
|
| 295 |
<div className="flex items-center gap-5">
|
|
|
|
| 178 |
{/* HEADER */}
|
| 179 |
<div className="bg-[var(--color-card)] border border-[var(--color-border)] rounded-2xl p-6 mb-6 flex justify-between items-center shadow-sm">
|
| 180 |
<div>
|
| 181 |
+
<Link href="/sessions" className="text-xs text-[var(--color-dimmer)] hover:text-[var(--color-text)] transition-colors mb-2 inline-block uppercase font-bold tracking-wider">← Back to Candidate Pools</Link>
|
| 182 |
+
<h1 className="text-3xl font-bold tracking-tight text-[var(--color-brand-light)]">🛡️ {session.name}</h1>
|
| 183 |
+
<p className="text-sm text-[var(--color-muted)] mt-1">{session.description || "Active Candidate Data Pool"}</p>
|
| 184 |
</div>
|
| 185 |
<div className="text-right">
|
| 186 |
<div className="text-3xl font-bold text-[var(--color-brand-light)]">{session.candidate_count.toLocaleString()}</div>
|
|
|
|
| 289 |
{match.results.map((c) => (
|
| 290 |
<Link
|
| 291 |
key={c.candidate_id}
|
| 292 |
+
href={`/jds/${selectedJD}/candidates/${c.candidate_id}?batch=${sessionId}`}
|
| 293 |
className="flex items-center justify-between p-4 bg-[var(--color-surface-2)] border border-[var(--color-border)] rounded-xl hover:border-[var(--color-brand-light)] hover:bg-[var(--color-card)] hover:shadow-lg transition-all group"
|
| 294 |
>
|
| 295 |
<div className="flex items-center gap-5">
|
frontend/src/app/sessions/page.tsx
CHANGED
|
@@ -44,13 +44,12 @@ export default function SessionsListPage() {
|
|
| 44 |
if (error) return <div className="p-8 text-[var(--color-danger)] text-center font-semibold">Error: {error}</div>;
|
| 45 |
|
| 46 |
return (
|
| 47 |
-
<div className="max-w-7xl mx-auto px-6 py-10">
|
| 48 |
<div className="flex items-center justify-between mb-8">
|
| 49 |
<div>
|
| 50 |
-
<h1 className="text-3xl font-bold tracking-tight mb-2">
|
| 51 |
-
<p className="text-[var(--color-muted)]">Select
|
| 52 |
</div>
|
| 53 |
-
<Link href="/pipeline" className="flex items-center gap-2 px-
|
| 54 |
<LayoutDashboard className="w-4 h-4" /> Run New Pipeline
|
| 55 |
</Link>
|
| 56 |
</div>
|
|
@@ -92,8 +91,8 @@ export default function SessionsListPage() {
|
|
| 92 |
</div>
|
| 93 |
|
| 94 |
<div className="mt-4 pt-4 border-t border-[var(--color-border)]/50 flex justify-between items-center gap-3">
|
| 95 |
-
<div className="text-[10px] font-
|
| 96 |
-
<button onClick={(e) => handleCopy(e, s.id)} className="p-1.5 rounded hover:bg-[var(--color-surface-2)] text-[var(--color-
|
| 97 |
{copiedId === s.id ? <CheckCircle2 className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
| 98 |
</button>
|
| 99 |
</div>
|
|
|
|
| 44 |
if (error) return <div className="p-8 text-[var(--color-danger)] text-center font-semibold">Error: {error}</div>;
|
| 45 |
|
| 46 |
return (
|
|
|
|
| 47 |
<div className="flex items-center justify-between mb-8">
|
| 48 |
<div>
|
| 49 |
+
<h1 className="text-3xl font-bold tracking-tight mb-2 text-[var(--color-brand-light)]">Candidate Pools</h1>
|
| 50 |
+
<p className="text-[var(--color-muted)] text-sm">Select a candidate batch below to interact with its associated semantic vectors.</p>
|
| 51 |
</div>
|
| 52 |
+
<Link href="/pipeline" className="flex items-center gap-2 px-5 py-3 bg-[var(--color-brand)] border border-[var(--color-brand-glow)] hover:bg-[var(--color-brand-light)] hover:shadow-[0_0_20px_var(--color-brand-dim)] transition-all rounded-xl text-xs font-bold uppercase tracking-wider text-white">
|
| 53 |
<LayoutDashboard className="w-4 h-4" /> Run New Pipeline
|
| 54 |
</Link>
|
| 55 |
</div>
|
|
|
|
| 91 |
</div>
|
| 92 |
|
| 93 |
<div className="mt-4 pt-4 border-t border-[var(--color-border)]/50 flex justify-between items-center gap-3">
|
| 94 |
+
<div className="text-[10px] uppercase font-bold text-[var(--color-dimmer)] tracking-widest flex-1">Metadata Verified</div>
|
| 95 |
+
<button onClick={(e) => handleCopy(e, s.id)} title="Copy Ingestion ID" className="p-1.5 rounded hover:bg-[var(--color-surface-2)] text-[var(--color-dimmer)] hover:text-[var(--color-text)] transition-colors">
|
| 96 |
{copiedId === s.id ? <CheckCircle2 className="w-3.5 h-3.5 text-green-400" /> : <Copy className="w-3.5 h-3.5" />}
|
| 97 |
</button>
|
| 98 |
</div>
|
supervisord.conf
CHANGED
|
@@ -9,8 +9,10 @@ command=uvicorn main:app --host 127.0.0.1 --port 8000 --workers 1
|
|
| 9 |
directory=/app/backend
|
| 10 |
autostart=true
|
| 11 |
autorestart=true
|
| 12 |
-
stdout_logfile=/
|
| 13 |
-
|
|
|
|
|
|
|
| 14 |
environment=PYTHONUNBUFFERED="1"
|
| 15 |
|
| 16 |
[program:frontend]
|
|
@@ -18,8 +20,10 @@ command=node server.js
|
|
| 18 |
directory=/app/frontend
|
| 19 |
autostart=true
|
| 20 |
autorestart=true
|
| 21 |
-
stdout_logfile=/
|
| 22 |
-
|
|
|
|
|
|
|
| 23 |
environment=NODE_ENV="production",PORT="3000",HOSTNAME="127.0.0.1"
|
| 24 |
|
| 25 |
[program:worker]
|
|
@@ -27,13 +31,17 @@ command=celery -A src.workers.celery_app.celery_app worker --loglevel=info --con
|
|
| 27 |
directory=/app/backend
|
| 28 |
autostart=true
|
| 29 |
autorestart=true
|
| 30 |
-
stdout_logfile=/
|
| 31 |
-
|
|
|
|
|
|
|
| 32 |
environment=PYTHONUNBUFFERED="1"
|
| 33 |
|
| 34 |
[program:nginx]
|
| 35 |
command=nginx -c /app/nginx.conf -g "daemon off;"
|
| 36 |
autostart=true
|
| 37 |
autorestart=true
|
| 38 |
-
stdout_logfile=/
|
| 39 |
-
|
|
|
|
|
|
|
|
|
| 9 |
directory=/app/backend
|
| 10 |
autostart=true
|
| 11 |
autorestart=true
|
| 12 |
+
stdout_logfile=/dev/stdout
|
| 13 |
+
stdout_logfile_maxbytes=0
|
| 14 |
+
stderr_logfile=/dev/stderr
|
| 15 |
+
stderr_logfile_maxbytes=0
|
| 16 |
environment=PYTHONUNBUFFERED="1"
|
| 17 |
|
| 18 |
[program:frontend]
|
|
|
|
| 20 |
directory=/app/frontend
|
| 21 |
autostart=true
|
| 22 |
autorestart=true
|
| 23 |
+
stdout_logfile=/dev/stdout
|
| 24 |
+
stdout_logfile_maxbytes=0
|
| 25 |
+
stderr_logfile=/dev/stderr
|
| 26 |
+
stderr_logfile_maxbytes=0
|
| 27 |
environment=NODE_ENV="production",PORT="3000",HOSTNAME="127.0.0.1"
|
| 28 |
|
| 29 |
[program:worker]
|
|
|
|
| 31 |
directory=/app/backend
|
| 32 |
autostart=true
|
| 33 |
autorestart=true
|
| 34 |
+
stdout_logfile=/dev/stdout
|
| 35 |
+
stdout_logfile_maxbytes=0
|
| 36 |
+
stderr_logfile=/dev/stderr
|
| 37 |
+
stderr_logfile_maxbytes=0
|
| 38 |
environment=PYTHONUNBUFFERED="1"
|
| 39 |
|
| 40 |
[program:nginx]
|
| 41 |
command=nginx -c /app/nginx.conf -g "daemon off;"
|
| 42 |
autostart=true
|
| 43 |
autorestart=true
|
| 44 |
+
stdout_logfile=/dev/stdout
|
| 45 |
+
stdout_logfile_maxbytes=0
|
| 46 |
+
stderr_logfile=/dev/stderr
|
| 47 |
+
stderr_logfile_maxbytes=0
|