|
import { json } from "@remix-run/node"; |
|
import type { LoaderFunctionArgs } from "@remix-run/node"; |
|
import { useLoaderData, Link } from "@remix-run/react"; |
|
|
|
export async function loader({ request }: LoaderFunctionArgs) { |
|
|
|
const envCheck = { |
|
GITHUB_APP_ID: { |
|
value: process.env.GITHUB_APP_ID || null, |
|
required: true, |
|
example: "your-app-id", |
|
}, |
|
GITHUB_APP_NAME: { |
|
value: process.env.GITHUB_APP_NAME || null, |
|
required: true, |
|
example: "your-app-name", |
|
}, |
|
GITHUB_APP_PRIVATE_KEY: { |
|
value: process.env.GITHUB_APP_PRIVATE_KEY ? "β Set" : null, |
|
required: true, |
|
example: "-----BEGIN RSA PRIVATE KEY-----...", |
|
}, |
|
GITHUB_APP_CLIENT_ID: { |
|
value: process.env.GITHUB_APP_CLIENT_ID || null, |
|
required: true, |
|
example: "your-client-id", |
|
}, |
|
GITHUB_APP_CLIENT_SECRET: { |
|
value: process.env.GITHUB_APP_CLIENT_SECRET ? "β Set" : null, |
|
required: true, |
|
example: "your-client-secret", |
|
}, |
|
GITHUB_WEBHOOK_SECRET: { |
|
value: process.env.GITHUB_WEBHOOK_SECRET ? "β Set" : null, |
|
required: false, |
|
example: "your-webhook-secret", |
|
}, |
|
GITHUB_CALLBACK_URL: { |
|
value: process.env.GITHUB_CALLBACK_URL || null, |
|
required: true, |
|
example: "http://localhost:3000/auth/github/callback", |
|
}, |
|
SESSION_SECRET: { |
|
value: process.env.SESSION_SECRET ? "β Set" : null, |
|
required: true, |
|
example: "your-session-secret", |
|
}, |
|
}; |
|
|
|
const missing = Object.entries(envCheck).filter( |
|
([key, config]) => config.required && !config.value |
|
); |
|
|
|
const isReady = missing.length === 0; |
|
|
|
return json({ |
|
envCheck, |
|
missing, |
|
isReady, |
|
nodeEnv: process.env.NODE_ENV || "development", |
|
}); |
|
} |
|
|
|
export default function Status() { |
|
const { envCheck, missing, isReady, nodeEnv } = useLoaderData<typeof loader>(); |
|
|
|
return ( |
|
<div className="min-h-screen bg-gray-50 py-8"> |
|
<div className="max-w-4xl mx-auto px-4"> |
|
<div className="bg-white rounded-lg shadow-md p-6"> |
|
<div className="flex items-center justify-between mb-6"> |
|
<h1 className="text-2xl font-bold text-gray-900"> |
|
Environment Status |
|
</h1> |
|
<Link |
|
to="/" |
|
className="text-blue-600 hover:text-blue-700 text-sm font-medium" |
|
> |
|
β Back to Home |
|
</Link> |
|
</div> |
|
|
|
{/* Status Overview */} |
|
<div className="mb-8"> |
|
<div className={`p-4 rounded-lg border ${ |
|
isReady |
|
? 'bg-green-50 border-green-200' |
|
: 'bg-red-50 border-red-200' |
|
}`}> |
|
<div className="flex items-center"> |
|
<div className="flex-shrink-0"> |
|
{isReady ? ( |
|
<svg className="h-5 w-5 text-green-400" viewBox="0 0 20 20" fill="currentColor"> |
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> |
|
</svg> |
|
) : ( |
|
<svg className="h-5 w-5 text-red-400" viewBox="0 0 20 20" fill="currentColor"> |
|
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clipRule="evenodd" /> |
|
</svg> |
|
)} |
|
</div> |
|
<div className="ml-3"> |
|
<h3 className={`text-sm font-medium ${ |
|
isReady ? 'text-green-800' : 'text-red-800' |
|
}`}> |
|
{isReady ? 'β
Ready to go!' : 'β οΈ Configuration needed'} |
|
</h3> |
|
<div className={`mt-2 text-sm ${ |
|
isReady ? 'text-green-700' : 'text-red-700' |
|
}`}> |
|
{isReady ? ( |
|
<p>All required environment variables are configured.</p> |
|
) : ( |
|
<p> |
|
{missing.length} required environment variable{missing.length > 1 ? 's' : ''} missing. |
|
</p> |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
{/* Environment Variables Table */} |
|
<div className="mb-8"> |
|
<h2 className="text-lg font-semibold text-gray-900 mb-4"> |
|
Environment Variables |
|
</h2> |
|
<div className="overflow-hidden shadow ring-1 ring-black ring-opacity-5 md:rounded-lg"> |
|
<table className="min-w-full divide-y divide-gray-300"> |
|
<thead className="bg-gray-50"> |
|
<tr> |
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |
|
Variable |
|
</th> |
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |
|
Status |
|
</th> |
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |
|
Required |
|
</th> |
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> |
|
Example |
|
</th> |
|
</tr> |
|
</thead> |
|
<tbody className="bg-white divide-y divide-gray-200"> |
|
{Object.entries(envCheck).map(([key, config]) => ( |
|
<tr key={key}> |
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"> |
|
{key} |
|
</td> |
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
|
{config.value ? ( |
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800"> |
|
{config.value} |
|
</span> |
|
) : ( |
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800"> |
|
Not set |
|
</span> |
|
)} |
|
</td> |
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> |
|
{config.required ? 'β
Yes' : 'βͺ Optional'} |
|
</td> |
|
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500 font-mono"> |
|
{config.example} |
|
</td> |
|
</tr> |
|
))} |
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
|
|
{/* Missing Variables */} |
|
{missing.length > 0 && ( |
|
<div className="mb-8"> |
|
<h2 className="text-lg font-semibold text-gray-900 mb-4"> |
|
Missing Configuration |
|
</h2> |
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4"> |
|
<div className="flex"> |
|
<div className="flex-shrink-0"> |
|
<svg className="h-5 w-5 text-yellow-400" viewBox="0 0 20 20" fill="currentColor"> |
|
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" /> |
|
</svg> |
|
</div> |
|
<div className="ml-3"> |
|
<h3 className="text-sm font-medium text-yellow-800"> |
|
Add these to your .env file: |
|
</h3> |
|
<div className="mt-2 text-sm text-yellow-700"> |
|
<div className="bg-gray-900 text-green-400 p-3 rounded font-mono text-xs overflow-x-auto"> |
|
{missing.map(([key, config]) => ( |
|
<div key={key}> |
|
{key}={config.example} |
|
</div> |
|
))} |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
)} |
|
|
|
{/* Quick Actions */} |
|
<div className="bg-gray-50 rounded-lg p-4"> |
|
<h3 className="text-lg font-semibold text-gray-900 mb-4"> |
|
Quick Actions |
|
</h3> |
|
<div className="grid md:grid-cols-2 gap-4"> |
|
<div> |
|
<h4 className="font-medium text-gray-900 mb-2">Generate Secrets</h4> |
|
<p className="text-sm text-gray-600 mb-3"> |
|
Generate random secrets for SESSION_SECRET and GITHUB_WEBHOOK_SECRET: |
|
</p> |
|
<code className="text-xs bg-gray-800 text-green-400 p-2 rounded block"> |
|
npm run secrets |
|
</code> |
|
</div> |
|
<div> |
|
<h4 className="font-medium text-gray-900 mb-2">Environment</h4> |
|
<p className="text-sm text-gray-600 mb-2"> |
|
Current environment: <span className="font-mono">{nodeEnv}</span> |
|
</p> |
|
<p className="text-sm text-gray-600"> |
|
{nodeEnv === 'development' ? ( |
|
<>β
Development mode - using .env file</> |
|
) : ( |
|
<>π Production mode - using system environment</> |
|
)} |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|