Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 86 additions & 27 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,43 +1,102 @@
# .env.example
#
# INSTRUCTIONS:
# 1. Copy this file to .env.local
# 2. To connect to the Poplar backend, set up an SSH tunnel mapping Poplar's
# internal port 8000 to your local listening port (e.g., 8000):
# 1) Copy to .env.local
# 2) Choose deployment mode:
# - NEXT_PUBLIC_DEPLOYMENT_MODE=staging|production (recommended)
# - NEXT_PUBLIC_DEPLOYMENT_MODE=manual for strict override mode (requires vars below)
#
# ssh -L 8000:localhost:8000 YOUR_USERNAME@poplar.cels.anl.gov
#
# 3. Then, ensure NEXT_PUBLIC_MODELSEED_API_URL is set to http://localhost:8000
# Local Poplar tunnel example:
# ssh -L 8000:localhost:8000 YOUR_USERNAME@poplar.cels.anl.gov

# --- API Configuration ---
# =========================
# Deployment Mode (required - set to staging, production, or manual)
# =========================
NEXT_PUBLIC_DEPLOYMENT_MODE=staging

# Point this to your local port for the SSH tunnel.
NEXT_PUBLIC_MODELSEED_API_URL=http://localhost:8000
# =========================
# Site URL defaults + override
# =========================
# Override (highest precedence, no trailing slash)
NEXT_PUBLIC_SITE_BASE_URL=
# Mode defaults
NEXT_PUBLIC_SITE_BASE_URL_STAGING=https://staging.modelseed.org
NEXT_PUBLIC_SITE_BASE_URL_PRODUCTION=https://modelseed.org

# When true, user data pages (My Models, My Media, etc.) will use the
# new modelseed-api backend instead of legacy services. (Default: true)
NEXT_PUBLIC_USE_MODELSEED_API=true
# =========================
# modelseed-api defaults + override
# =========================
# Override (highest precedence, no trailing slash)
# Example local tunnel:
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
# Mode defaults
NEXT_PUBLIC_API_BASE_URL_STAGING=https://staging.modelseed.org/PMS
NEXT_PUBLIC_API_BASE_URL_PRODUCTION=https://modelseed.org/PMS

# =========================
# Legacy REST defaults + override (/api/v0)
# =========================
NEXT_PUBLIC_REST_BASE_URL=
NEXT_PUBLIC_REST_BASE_URL_STAGING=https://staging.modelseed.org/api/v0
NEXT_PUBLIC_REST_BASE_URL_PRODUCTION=https://modelseed.org/api/v0

# =========================
# Status endpoint defaults + override (/about/version)
# =========================
NEXT_PUBLIC_STATUS_API_URL=
NEXT_PUBLIC_STATUS_API_URL_STAGING=https://staging.modelseed.org/api/test-service
NEXT_PUBLIC_STATUS_API_URL_PRODUCTION=https://modelseed.org/api/test-service

# When true, Workspace calls are routed through the new unified REST proxy
# on the modelseed-api backend. (Default: true)
# NOTE: Operations like `workspaceUpdateMetadata` strictly require the proxy.
# =========================
# Solr base defaults + override
# =========================
# Override (highest precedence, with or without trailing slash)
NEXT_PUBLIC_SOLR_BASE_URL=
# Mode defaults
NEXT_PUBLIC_SOLR_BASE_URL_STAGING=https://staging.modelseed.org/solr/
NEXT_PUBLIC_SOLR_BASE_URL_PRODUCTION=https://modelseed.org/solr/

# =========================
# Solr collection/core defaults + override
# =========================
# Overrides (highest precedence)
NEXT_PUBLIC_SOLR_REACTIONS_COLLECTION=
NEXT_PUBLIC_SOLR_COMPOUNDS_COLLECTION=
# Mode defaults
NEXT_PUBLIC_SOLR_REACTIONS_COLLECTION_STAGING=reactions_staging
NEXT_PUBLIC_SOLR_REACTIONS_COLLECTION_PRODUCTION=reactions
NEXT_PUBLIC_SOLR_COMPOUNDS_COLLECTION_STAGING=compounds_staging
NEXT_PUBLIC_SOLR_COMPOUNDS_COLLECTION_PRODUCTION=compounds

# =========================
# Feature Flags
# =========================
NEXT_PUBLIC_USE_MODELSEED_API=true
NEXT_PUBLIC_USE_NEW_PROXY=true

# When true, Biochemistry calls (reactions/compounds) are routed through
# the new modelseed-api. (Default: false - keep on Solr for now)
NEXT_PUBLIC_USE_NEW_BIOCHEM=false
# =========================
# ProbModelSEED URL Override
# =========================
# Override for ProbModelSEED API endpoint (no trailing slash)
# Defaults to {SITE_BASE}/api/model when USE_NEW_PROXY=true
NEXT_PUBLIC_PROBMODELSEED_URL=

# =========================
# RDKit.js URL Override (optional)
# =========================
# Override for RDKit.js assets (no trailing slash)
# Defaults to unpkg CDN when unset - no need to set unless self-hosting
# NEXT_PUBLIC_RDKIT_BASE_URL=

# --- Version Page Metadata ---
# Shown on /about/version
# =========================
# Build Metadata (/about/version)
# =========================
NEXT_PUBLIC_GIT_VERSION=3.0.0
NEXT_PUBLIC_GIT_BRANCH=staging
NEXT_PUBLIC_GIT_COMMIT=
# Optional build date override shown on /about/version
NEXT_PUBLIC_DEPLOY_DATE=

# --- Test Credentials ---
# For E2E tests, you can provide a PATRIC token or username/password.
# Copy these to .env.local and fill them in.
# =========================
# Test Credentials
# =========================
# PATRIC token for API testing (get from PATRIC website)
PATRIC_TOKEN=
PATRIC_USERNAME=
PATRIC_PASSWORD=
2 changes: 1 addition & 1 deletion .gsd/milestones/v1-alpha/3/4-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Rebuild the `/projects` and `/events` hubs, replicating the simple layout struct
- Set up the grid blocks (`<md-content layout-margin layout="row">...</div>`) into MUI Grid/Flex components (using `Box` or `Stack` or equivalent inline CSS `display: flex`).
- Build links (using `<Link>` vs `<a>` where appropriate):
- Internal: `href="/projects/fusions"`, `href="/projects/regulons"`
- External: `http://komodo.modelseed.org`, `http://minedatabase.mcs.anl.gov`, `http://coremodels.mcs.anl.gov`.
- External: `http://komodo.modelseed.org`, `https://minedatabase.mcs.anl.gov`, `http://coremodels.mcs.anl.gov`.
- Fix missing image references `ms-projects/img/atomic-regulons.png` (can use placeholders if image doesn't exist locally, or verify we copied those in Phase 1).
</action>
<verify>npm run check</verify>
Expand Down
2 changes: 1 addition & 1 deletion .gsd/milestones/v1-alpha/3/VERIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ File: lib/data/publications.ts (48317 bytes)
29: Link href="/projects/fusions" (internal)
42: href="http://komodo.modelseed.org" (external, target="_blank")
66: Link href="/projects/regulons" (internal)
87: href="http://minedatabase.mcs.anl.gov" (external, target="_blank")
87: href="https://minedatabase.mcs.anl.gov" (external, target="_blank")
103: href="http://coremodels.mcs.anl.gov" (external, target="_blank")
```
**Screenshot:** `phase3_projects_1772578681570.png` — Shows "ModelSEED Projects" heading, two-column grid with:
Expand Down
24 changes: 23 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,31 @@ RUN npm ci
COPY . .

# Tell Docker to expect these variables during the build
ARG NEXT_PUBLIC_MODELSEED_API_URL
ARG NEXT_PUBLIC_DEPLOYMENT_MODE=staging
ARG NEXT_PUBLIC_SITE_BASE_URL
ARG NEXT_PUBLIC_SITE_BASE_URL_STAGING
ARG NEXT_PUBLIC_SITE_BASE_URL_PRODUCTION
ARG NEXT_PUBLIC_API_BASE_URL
ARG NEXT_PUBLIC_API_BASE_URL_STAGING
ARG NEXT_PUBLIC_API_BASE_URL_PRODUCTION
ARG NEXT_PUBLIC_REST_BASE_URL
ARG NEXT_PUBLIC_REST_BASE_URL_STAGING
ARG NEXT_PUBLIC_REST_BASE_URL_PRODUCTION
ARG NEXT_PUBLIC_STATUS_API_URL
ARG NEXT_PUBLIC_STATUS_API_URL_STAGING
ARG NEXT_PUBLIC_STATUS_API_URL_PRODUCTION
ARG NEXT_PUBLIC_SOLR_BASE_URL
ARG NEXT_PUBLIC_SOLR_BASE_URL_STAGING
ARG NEXT_PUBLIC_SOLR_BASE_URL_PRODUCTION
ARG NEXT_PUBLIC_SOLR_REACTIONS_COLLECTION
ARG NEXT_PUBLIC_SOLR_REACTIONS_COLLECTION_STAGING
ARG NEXT_PUBLIC_SOLR_REACTIONS_COLLECTION_PRODUCTION
ARG NEXT_PUBLIC_SOLR_COMPOUNDS_COLLECTION
ARG NEXT_PUBLIC_SOLR_COMPOUNDS_COLLECTION_STAGING
ARG NEXT_PUBLIC_SOLR_COMPOUNDS_COLLECTION_PRODUCTION
ARG NEXT_PUBLIC_USE_MODELSEED_API
ARG NEXT_PUBLIC_USE_NEW_PROXY
ARG NEXT_PUBLIC_PROBMODELSEED_URL
ARG NEXT_PUBLIC_GIT_VERSION
ARG NEXT_PUBLIC_GIT_COMMIT
ARG NEXT_PUBLIC_GIT_BRANCH
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ ModelSEED-UI is the modern Next.js 16 + React 19 + MUI 7 interface for the Model

Key configuration constants live in `lib/api/config.ts`:

- `DEPLOYMENT_MODE` – `NEXT_PUBLIC_DEPLOYMENT_MODE` (`staging|production|manual`). Unset defaults to `staging`; use `manual` for strict override mode.
- `MODELSEED_API_URL` – base URL for Poplar (currently `http://poplar.cels.anl.gov:8000` in development).
- `USE_MODELSEED_API` – when `true`, user data flows (My Models, My Media, jobs) use `modelseed-api`.
- `USE_NEW_PROXY` – when `true`, workspace calls route through the REST proxy at `${MODELSEED_API_URL}/api/workspace`.
- `SOLR_BASE` / `USE_NEW_BIOCHEM` – control whether biochem lookups use legacy Solr or the new biochem endpoints (by default, tables stay on Solr).
- `SOLR_BASE` / `SOLR_REACTIONS_COLLECTION` / `SOLR_COMPOUNDS_COLLECTION` – control Solr endpoint and core selection for biochem pages.

## Running the App Locally

Expand Down Expand Up @@ -156,7 +157,7 @@ When initializing a debugging or feature session, start by reading `INDEX.md` fo
3. **Configure `.env.local`:**
```bash
# When using SSH tunnel
NEXT_PUBLIC_MODELSEED_API_URL=http://localhost:8000
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000

# For authenticated tests
PATRIC_USERNAME=your_username
Expand Down Expand Up @@ -197,7 +198,7 @@ See [`tests/README.md`](tests/README.md) for full documentation.
1. Check SSH tunnel is running (see above)
2. Verify environment variable in `.env.local`:
```bash
NEXT_PUBLIC_MODELSEED_API_URL=http://localhost:8000
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000
```
3. Restart the development server after changing `.env.local`
4. Test API connection: `curl http://localhost:8000/api/media/public`
Expand Down
2 changes: 1 addition & 1 deletion app/(user-data)/my-models/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export default function MyModelsPage() {
// for the legacy paths. Instead, surface a clear configuration
// error so the environment can be fixed explicitly.
throw new Error(
'My Models requires modelseed-api. Set NEXT_PUBLIC_USE_MODELSEED_API=true and point NEXT_PUBLIC_MODELSEED_API_URL at a running modelseed-api instance.',
'My Models requires modelseed-api. Set NEXT_PUBLIC_USE_MODELSEED_API=true and point NEXT_PUBLIC_API_BASE_URL at a running modelseed-api instance.',
);
},
staleTime: 30 * 1000,
Expand Down
2 changes: 1 addition & 1 deletion app/(user-data)/myMedia/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function MyMediaPage() {
}

throw new Error(
'My Media requires modelseed-api. Set NEXT_PUBLIC_USE_MODELSEED_API=true and point NEXT_PUBLIC_MODELSEED_API_URL at a running modelseed-api instance.',
'My Media requires modelseed-api. Set NEXT_PUBLIC_USE_MODELSEED_API=true and point NEXT_PUBLIC_API_BASE_URL at a running modelseed-api instance.',
);
},
staleTime: 5 * 60 * 1000,
Expand Down
6 changes: 4 additions & 2 deletions app/about/version/StatusTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import { useAuth } from '@/components/auth/AuthProvider';
import {
MODELSEED_API_TEST_URL,
MODELSEED_API_URL,
PROBMODELSEED_URL,
SOLR_BASE_LEGACY,
USE_MODELSEED_API,
USE_NEW_PROXY,
WORKSPACE_URL,
Expand Down Expand Up @@ -60,8 +62,8 @@ const SERVICES: ServiceConfig[] = [
{ id: 'auth', service: 'RAST Auth', endpoint: 'https://p3.theseed.org/Sessions/Login', pingUrl: null, authReq: false },
{ id: 'patric', service: 'PATRIC Auth', endpoint: 'https://user.patricbrc.org/authenticate', pingUrl: null, authReq: false },
{ id: 'shock', service: 'Shock', endpoint: 'https://p3.theseed.org/services/shock_api', link: 'https://github.com/MG-RAST/Shock', pingUrl: 'https://p3.theseed.org/services/shock_api/', authReq: false, api: [{ label: 'GitHub', url: 'https://github.com/MG-RAST/Shock' }] },
{ id: 'solr', service: 'SOLR', endpoint: 'https://modelseed.org/solr/', pingUrl: 'https://modelseed.org/solr/', authReq: false },
{ id: 'api', service: 'API', endpoint: 'https://modelseed.org/api/test-service', pingUrl: 'https://modelseed.org/api/test-service', authReq: false },
{ id: 'solr', service: 'SOLR', endpoint: SOLR_BASE_LEGACY, pingUrl: SOLR_BASE_LEGACY, authReq: false },
{ id: 'api', service: 'API', endpoint: MODELSEED_API_TEST_URL, pingUrl: MODELSEED_API_TEST_URL, authReq: false },
{
id: 'pms',
service: 'ProbModelSEED',
Expand Down
6 changes: 5 additions & 1 deletion app/about/version/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Link from '@mui/material/Link';
import Image from 'next/image';
import ReactMarkdown from 'react-markdown';
import StatusTable from '@/app/about/version/StatusTable';
import { DEPLOY_ENV_LABEL } from '@/lib/api/config';

/**
* Reads CHANGELOG.md file from project root.
Expand All @@ -37,6 +38,7 @@ type BuildMetadata = {
commit: string;
branch: string;
date: string;
environment: string;
};

function getVersion(): string {
Expand Down Expand Up @@ -76,6 +78,7 @@ function getBuildMetadata(): BuildMetadata {
branch: getBranchName(),
commit: getCommitSha(),
date: getBuildDate(),
environment: DEPLOY_ENV_LABEL,
};
}

Expand Down Expand Up @@ -110,7 +113,8 @@ export default async function VersionPage() {
</Link><br />
Branch: {metadata.branch}<br />
Commit: {metadata.commit}<br />
Date: {metadata.date}
Date: {metadata.date}<br />
Environment: {metadata.environment}
</Typography>
</Box>
<Box>
Expand Down
109 changes: 109 additions & 0 deletions app/api/biochem/comments/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { NextRequest, NextResponse } from 'next/server';
import { MODELSEED_REST_URL } from '@/lib/api/config';

interface CommentRequestBody {
reactionId?: unknown;
isAlias?: unknown;
wrongStoichiometry?: unknown;
remarks?: unknown;
email?: unknown;
username?: unknown;
}

export async function POST(request: NextRequest) {
let body: CommentRequestBody;
try {
body = (await request.json()) as CommentRequestBody;
} catch {
return NextResponse.json({ message: 'Invalid JSON body' }, { status: 400 });
}

const reactionId = typeof body.reactionId === 'string' ? body.reactionId.trim() : '';
const remarks = typeof body.remarks === 'string' ? body.remarks.trim() : '';
const email = typeof body.email === 'string' ? body.email.trim() : '';
const username = typeof body.username === 'string' ? body.username.trim() : '';
const isAlias = typeof body.isAlias === 'boolean' ? body.isAlias : false;
const wrongStoichiometry = typeof body.wrongStoichiometry === 'boolean' ? body.wrongStoichiometry : false;

if (!reactionId) {
return NextResponse.json({ message: 'reactionId is required' }, { status: 400 });
}

const selectedComments: string[] = [];
if (isAlias) selectedComments.push('incorrect alias');
if (wrongStoichiometry) selectedComments.push('incorrect stoichiometry');

if (!remarks && selectedComments.length === 0) {
return NextResponse.json(
{ message: 'Select at least one issue or provide remarks before submitting.' },
{ status: 400 },
);
}

const legacyPayload = {
user: {
...(username ? { username } : {}),
...(email ? { email } : {}),
...(remarks ? { remarks } : {}),
},
rowId: reactionId,
comments: selectedComments,
};

const formData = new URLSearchParams({
comment: JSON.stringify(legacyPayload),
});

const upstreamAuth = request.headers.get('x-modelseed-auth');
try {
const response = await fetch(`${MODELSEED_REST_URL}/comments`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
...(upstreamAuth ? { Authorization: upstreamAuth } : {}),
},
body: formData.toString(),
});

const rawText = await response.text();
let payload: unknown = null;
if (rawText) {
try {
payload = JSON.parse(rawText);
} catch {
payload = { message: rawText };
}
}

if (!response.ok) {
const detail =
typeof payload === 'object' &&
payload !== null &&
typeof (payload as { msg?: unknown; message?: unknown }).msg === 'string'
? (payload as { msg: string }).msg
: typeof payload === 'object' &&
payload !== null &&
typeof (payload as { message?: unknown }).message === 'string'
? (payload as { message: string }).message
: rawText || `HTTP ${response.status}`;
return NextResponse.json({ message: detail }, { status: response.status });
}

const message =
typeof payload === 'object' &&
payload !== null &&
typeof (payload as { msg?: unknown; message?: unknown }).msg === 'string'
? (payload as { msg: string }).msg
: typeof payload === 'object' &&
payload !== null &&
typeof (payload as { message?: unknown }).message === 'string'
? (payload as { message: string }).message
: `Comment submitted for ${reactionId}.`;

return NextResponse.json({ msg: message });
} catch (error) {
const message = error instanceof Error ? error.message : 'Unable to reach comment service.';
return NextResponse.json({ message }, { status: 502 });
}
}
Loading
Loading