Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ playwright-report/

# AI assistant/tooling metadata (keep local, do not track)
.agent/
.antigravitycli/
.cursor/
.gemini/
.cursorrules
Expand Down
23 changes: 17 additions & 6 deletions app/(build-model)/plant/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import Stack from '@mui/material/Stack';
import CircularProgress from '@mui/material/CircularProgress';
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import Link from 'next/link';
import AuthGuard from '@/components/auth/AuthGuard';
import { useAuth } from '@/components/auth/AuthProvider';
Expand Down Expand Up @@ -461,11 +460,23 @@ export default function BuildModelPlantPage() {
maxWidth="sm"
fullWidth
>
<DialogActions>
<Button onClick={() => setPlantseedDialogOpen(false)}>
Close
</Button>
</DialogActions>
<Box sx={{ p: 3 }}>
<Typography variant="h6" fontWeight={600} gutterBottom>
PlantSEED — Update In Progress
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary', mb: 2 }}>
PlantSEED v2.0 → PlantSEED v3.0
</Typography>
<Alert severity="info" sx={{ mb: 2 }}>
Annotation and reconstruction services are temporarily offline for updates
and will be restored shortly.
</Alert>
<Box sx={{ display: 'flex', justifyContent: 'flex-end' }}>
<Button onClick={() => setPlantseedDialogOpen(false)} variant="contained">
Close
</Button>
</Box>
</Box>
</Dialog>
</Box>
</AuthGuard >
Expand Down
34 changes: 6 additions & 28 deletions app/(reference-data)/biochem/reactions/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
import { getReactions, type Reaction, type SolrQueryOpts, EXTERNAL_DBS } from '@/lib/api/biochem';
import ChemicalEquation from '@/components/ui/ChemicalEquation';
import IconButton from '@mui/material/IconButton';
import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline';
import ReactionCommentModal from '@/components/ui/ReactionCommentModal';
/* import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; */
/* import ReactionCommentModal from '@/components/ui/ReactionCommentModal'; */
import { GridHighlightText } from '@/components/GridHighlightText';
import DataControlHeader from '@/components/layout/DataControlHeader';
import ExportModal from '@/components/ui/ExportModal';
Expand Down Expand Up @@ -211,8 +211,8 @@
}, [handleFilterModelChange]);

// Modal state
const [commentModalOpen, setCommentModalOpen] = useState(false);

Check warning on line 214 in app/(reference-data)/biochem/reactions/page.tsx

View workflow job for this annotation

GitHub Actions / Verify (lint, typecheck, test, build, audit)

'commentModalOpen' is assigned a value but never used
const [commentReactionId, setCommentReactionId] = useState<string | null>(null);

Check warning on line 215 in app/(reference-data)/biochem/reactions/page.tsx

View workflow job for this annotation

GitHub Actions / Verify (lint, typecheck, test, build, audit)

'commentReactionId' is assigned a value but never used
const [exportModalOpen, setExportModalOpen] = useState(false);
const [pathwaysModalOpen, setPathwaysModalOpen] = useState(false);
const [pathwaysModalReactionId, setPathwaysModalReactionId] = useState<string | null>(null);
Expand Down Expand Up @@ -272,23 +272,7 @@
</Link>
),
},
{
field: 'actions',
headerName: '',
width: 50,
sortable: false,
disableColumnMenu: true,
renderCell: (params) => (
<IconButton
size="small"
title="Comment on this reaction"
onClick={() => handleOpenComment(params.row.id)}
sx={{ color: '#00acc1' }}
>
<ChatBubbleOutlineIcon fontSize="small" />
</IconButton>
)
},
/* comment button column disabled */
{
field: 'name',
headerName: 'Name',
Expand Down Expand Up @@ -348,13 +332,7 @@
);
},
},
{
field: 'notes',
headerName: 'Notes',
width: 100,
sortable: false,
renderCell: (params) => <GridHighlightText text={(params.row.notes ?? []).join(' | ')} />,
},
/* notes column disabled */
{
field: 'synonyms',
headerName: 'Synonyms',
Expand Down Expand Up @@ -397,7 +375,7 @@
return row.ontology;
},
},
], [handleOpenComment, handleOpenPathwaysModal]);

Check warning on line 378 in app/(reference-data)/biochem/reactions/page.tsx

View workflow job for this annotation

GitHub Actions / Verify (lint, typecheck, test, build, audit)

React Hook useMemo has an unnecessary dependency: 'handleOpenComment'. Either exclude it or remove the dependency array

const queryOpts = useMemo<SolrQueryOpts>(() => ({
limit: paginationModel.pageSize,
Expand Down Expand Up @@ -463,11 +441,11 @@
autoHeight
/>

<ReactionCommentModal
{/* <ReactionCommentModal
open={commentModalOpen}
onClose={() => setCommentModalOpen(false)}
reactionId={commentReactionId}
/>
/> */}

<ExportModal
open={exportModalOpen}
Expand Down
57 changes: 57 additions & 0 deletions app/api/maintenance/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Maintenance mode status endpoint.
*
* Returns whether the site is in maintenance mode and an optional message.
* Operators can toggle this by:
* 1. Creating /tmp/maintenance.json with {"enabled":true,"message":"..."}
* (file is checked at runtime, no restart needed)
* 2. Setting MAINTENANCE_MODE env var (requires container restart)
*/
Comment on lines +1 to +9
import { NextResponse } from 'next/server';
import fs from 'fs';

const NO_CACHE_HEADERS = { 'Cache-Control': 'no-store, max-age=0' };

interface MaintenanceStatus {
enabled: boolean;
message: string;
}

function readFileStatus(): MaintenanceStatus | null {
try {
const raw = fs.readFileSync('/tmp/maintenance.json', 'utf-8');
const data = JSON.parse(raw);
if (data && typeof data.enabled === 'boolean') {
return {
enabled: data.enabled,
message: typeof data.message === 'string' ? data.message : '',
};
}
} catch {
// File doesn't exist or invalid — ignore
}
return null;
}

export async function GET(): Promise<NextResponse> {
// 1. Check file-based toggle first (runtime toggle, no restart needed)
const fileStatus = readFileStatus();
if (fileStatus) {
return NextResponse.json(fileStatus, { headers: NO_CACHE_HEADERS });
}

// 2. Fall back to env var (requires container restart)
const envEnabled = process.env.MAINTENANCE_MODE === 'true' || process.env.MAINTENANCE_MODE === '1';
if (envEnabled) {
return NextResponse.json({
enabled: true,
message: process.env.MAINTENANCE_MESSAGE || 'Site is undergoing maintenance. Please check back shortly.',
}, { headers: NO_CACHE_HEADERS });
}

// 3. Default: not in maintenance
return NextResponse.json(
{ enabled: false, message: '' },
{ headers: NO_CACHE_HEADERS },
);
}
Comment on lines +36 to +57
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import CssBaseline from '@mui/material/CssBaseline';
import theme from '@/lib/theme';
import Box from '@mui/material/Box';
import HeaderLayoutRouter from '@/app/HeaderLayoutRouter';
import OutageBanner from '@/components/ui/OutageBanner';
import Providers from '@/components/Providers';
import { AuthProvider } from '@/components/auth/AuthProvider';
import type { Metadata } from "next";
Expand Down Expand Up @@ -35,6 +36,7 @@ export default function RootLayout({
<Providers>
<AuthProvider>
<CssBaseline />
<OutageBanner />
<HeaderLayoutRouter />
<Box
component="main"
Expand Down
45 changes: 45 additions & 0 deletions components/ui/OutageBanner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client';

import { useEffect, useState } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import Alert from '@mui/material/Alert';

interface MaintenanceStatus {
enabled: boolean;
message: string;
}

export default function OutageBanner() {
const [status, setStatus] = useState<MaintenanceStatus | null>(null);

useEffect(() => {
fetch('/api/maintenance', { cache: 'no-store' })
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((data: MaintenanceStatus) => setStatus(data))
.catch(() => setStatus({ enabled: false, message: '' }));
}, []);

if (!status || !status.enabled) return null;

return (
<Box sx={{ width: '100%' }}>
<Alert
severity="warning"
sx={{
borderRadius: 0,
textAlign: 'center',
justifyContent: 'center',
'& .MuiAlert-message': { flex: 'unset' },
}}
>
<Typography variant="body1" fontWeight={600}>
{status.message || 'Site is undergoing maintenance. Please check back shortly.'}
</Typography>
</Alert>
</Box>
);
}
Loading