remove nginx
This commit is contained in:
@@ -777,13 +777,13 @@ services:
|
|||||||
- DATABASE_URL=sqlite:///data/astronome.db
|
- DATABASE_URL=sqlite:///data/astronome.db
|
||||||
- RUST_LOG=info
|
- RUST_LOG=info
|
||||||
ports:
|
ports:
|
||||||
- "3301:3301"
|
- "3001:3001"
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build: ./frontend
|
build: ./frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3300:80"
|
- "3000:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
@@ -801,7 +801,7 @@ services:
|
|||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
# nginx-conf.conf — reverse proxy, single origin for browser
|
# nginx-conf.conf — reverse proxy, single origin for browser
|
||||||
upstream backend { server backend:3301; }
|
upstream backend { server backend:3001; }
|
||||||
upstream frontend { server frontend:80; }
|
upstream frontend { server frontend:80; }
|
||||||
|
|
||||||
server {
|
server {
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "astronome"
|
name = "astronome"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "astronome"
|
name = "astronome"
|
||||||
|
|||||||
+11
-14
@@ -1,21 +1,18 @@
|
|||||||
FROM rust:latest AS builder
|
FROM rust:1.93.0-slim AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
|
||||||
ENV CARGO_HOME=/usr/local/cargo
|
|
||||||
ENV CARGO_TARGET_DIR=/app/target
|
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y pkg-config libssl-dev
|
|
||||||
|
|
||||||
COPY Cargo.toml ./
|
COPY Cargo.toml ./
|
||||||
|
# Create dummy main to cache dependencies
|
||||||
# clean build (important)
|
|
||||||
RUN cargo clean || true
|
|
||||||
|
|
||||||
RUN mkdir src && echo "fn main() {}" > src/main.rs
|
RUN mkdir src && echo "fn main() {}" > src/main.rs
|
||||||
RUN cargo build --release
|
RUN cargo build --release
|
||||||
|
|
||||||
RUN rm -rf src
|
RUN rm -rf src
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
|
# Force rebuild of main crate
|
||||||
|
RUN touch src/main.rs && cargo build --release
|
||||||
|
|
||||||
RUN cargo build --release
|
FROM debian:bookworm-slim
|
||||||
|
RUN apt-get update && apt-get install -y libssl3 ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
|
WORKDIR /app
|
||||||
|
RUN mkdir -p /data/gallery
|
||||||
|
COPY --from=builder /app/target/release/astronome /usr/local/bin/
|
||||||
|
CMD ["astronome"]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use axum::{extract::State, Json};
|
use axum::{extract::State, Json};
|
||||||
use chrono::{NaiveDateTime, Utc};
|
use chrono::NaiveDateTime;
|
||||||
|
|
||||||
use super::{AppError, AppState};
|
use super::{AppError, AppState};
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ pub mod solar;
|
|||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod visibility;
|
pub mod visibility;
|
||||||
|
|
||||||
pub use coords::{airmass, extinction_mag, radec_to_altaz};
|
pub use horizon::HorizonPoint;
|
||||||
pub use horizon::{horizon_alt, HorizonPoint};
|
|
||||||
pub use lunar::{moon_age_days, moon_altitude, moon_illumination, moon_phase_name, moon_position, moon_rise_set, moon_separation};
|
pub use lunar::{moon_age_days, moon_altitude, moon_illumination, moon_phase_name, moon_position, moon_rise_set, moon_separation};
|
||||||
pub use solar::astro_twilight;
|
pub use solar::astro_twilight;
|
||||||
pub use time::julian_date;
|
pub use time::julian_date;
|
||||||
|
|||||||
@@ -110,8 +110,8 @@ pub fn normalize_catalog_id(raw: &str) -> String {
|
|||||||
|
|
||||||
pub fn is_suitable(row: &RawCatalogRow) -> bool {
|
pub fn is_suitable(row: &RawCatalogRow) -> bool {
|
||||||
// Validate RA/Dec exist — required for all objects
|
// Validate RA/Dec exist — required for all objects
|
||||||
let Some(ra) = row.ra_deg() else { return false };
|
let Some(_ra) = row.ra_deg() else { return false };
|
||||||
let Some(dec) = row.dec_deg() else { return false };
|
let Some(_dec) = row.dec_deg() else { return false };
|
||||||
|
|
||||||
// Declination constraint: −30° ≤ Dec ≤ +75° (spec §5.2)
|
// Declination constraint: −30° ≤ Dec ≤ +75° (spec §5.2)
|
||||||
// if dec < -30.0 || dec > 75.0 {
|
// if dec < -30.0 || dec > 75.0 {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ fn parse_vizier_tsv(text: &str) -> Vec<VdbRow> {
|
|||||||
let mut header: Vec<String> = Vec::new();
|
let mut header: Vec<String> = Vec::new();
|
||||||
let mut found_separator = false;
|
let mut found_separator = false;
|
||||||
|
|
||||||
for (line_num, line) in text.lines().enumerate() {
|
for (_line_num, line) in text.lines().enumerate() {
|
||||||
// Skip comment/meta lines
|
// Skip comment/meta lines
|
||||||
if line.starts_with('#') {
|
if line.starts_with('#') {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
+1
-1
@@ -37,7 +37,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
let app = api::build_router(pool).layer(cors);
|
let app = api::build_router(pool).layer(cors);
|
||||||
|
|
||||||
let bind_addr = "0.0.0.0:3301";
|
let bind_addr = "0.0.0.0:3001";
|
||||||
tracing::info!("Starting server on {}", bind_addr);
|
tracing::info!("Starting server on {}", bind_addr);
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(bind_addr).await?;
|
let listener = tokio::net::TcpListener::bind(bind_addr).await?;
|
||||||
|
|||||||
+75
-5
@@ -181,11 +181,16 @@ pub fn parse_phd2_log(content: &str) -> anyhow::Result<Phd2Analysis> {
|
|||||||
snr_vals.iter().sum::<f64>() / snr_vals.len() as f64
|
snr_vals.iter().sum::<f64>() / snr_vals.len() as f64
|
||||||
};
|
};
|
||||||
|
|
||||||
let duration_sec = match (first_time, last_time) {
|
// Try to extract duration from guiding session timestamps first
|
||||||
(Some(f), Some(l)) => (l - f).max(0.0),
|
// If not available, fall back to CSV time span
|
||||||
_ => 0.0,
|
let duration_min = extract_guiding_duration(content)
|
||||||
};
|
.unwrap_or_else(|| {
|
||||||
let duration_min = (duration_sec / 60.0) as u32;
|
let duration_sec = match (first_time, last_time) {
|
||||||
|
(Some(f), Some(l)) => (l - f).max(0.0),
|
||||||
|
_ => 0.0,
|
||||||
|
};
|
||||||
|
(duration_sec / 60.0) as u32
|
||||||
|
});
|
||||||
|
|
||||||
// Simple linear drift: last half minus first half average
|
// Simple linear drift: last half minus first half average
|
||||||
let drift_ra = if n > 4.0 {
|
let drift_ra = if n > 4.0 {
|
||||||
@@ -304,3 +309,68 @@ fn extract_date_from_timestamp(timestamp: &str) -> Option<String> {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extract_guiding_duration(content: &str) -> Option<u32> {
|
||||||
|
// Find first "Guiding Begins at YYYY-MM-DD HH:MM:SS"
|
||||||
|
// Find last "Guiding Ends at YYYY-MM-DD HH:MM:SS"
|
||||||
|
// Calculate duration from the time span
|
||||||
|
|
||||||
|
let mut begins_timestamp: Option<&str> = None;
|
||||||
|
let mut ends_timestamp: Option<&str> = None;
|
||||||
|
|
||||||
|
for line in content.lines() {
|
||||||
|
if line.contains("Guiding Begins at ") && begins_timestamp.is_none() {
|
||||||
|
if let Some(idx) = line.find("Guiding Begins at ") {
|
||||||
|
let ts = &line[idx + 18..];
|
||||||
|
// Take first 19 chars: YYYY-MM-DD HH:MM:SS
|
||||||
|
if ts.len() >= 19 {
|
||||||
|
begins_timestamp = Some(&ts[..19]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if line.contains("Guiding Ends at ") {
|
||||||
|
if let Some(idx) = line.find("Guiding Ends at ") {
|
||||||
|
let ts = &line[idx + 16..];
|
||||||
|
// Take first 19 chars: YYYY-MM-DD HH:MM:SS
|
||||||
|
if ts.len() >= 19 {
|
||||||
|
ends_timestamp = Some(&ts[..19]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have both timestamps, calculate duration
|
||||||
|
if let (Some(begin), Some(end)) = (begins_timestamp, ends_timestamp) {
|
||||||
|
// Parse timestamps like "2026-03-17 20:04:53"
|
||||||
|
if let (Some(begin_dt), Some(end_dt)) = (parse_phd2_timestamp(begin), parse_phd2_timestamp(end)) {
|
||||||
|
let duration_secs = (end_dt - begin_dt).max(0.0);
|
||||||
|
return Some((duration_secs / 60.0) as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_phd2_timestamp(timestamp: &str) -> Option<f64> {
|
||||||
|
// Parse timestamp like "2026-03-17 20:04:53" to Unix seconds (or any consistent float)
|
||||||
|
// We just need the relative difference, so we can use a simple approach:
|
||||||
|
// Convert to total seconds for the day
|
||||||
|
if timestamp.len() < 19 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let year: u32 = timestamp[..4].parse().ok()?;
|
||||||
|
let month: u32 = timestamp[5..7].parse().ok()?;
|
||||||
|
let day: u32 = timestamp[8..10].parse().ok()?;
|
||||||
|
let hour: u32 = timestamp[11..13].parse().ok()?;
|
||||||
|
let min: u32 = timestamp[14..16].parse().ok()?;
|
||||||
|
let sec: u32 = timestamp[17..19].parse().ok()?;
|
||||||
|
|
||||||
|
// Use chrono to parse it properly
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
let naive_date = NaiveDate::from_ymd_opt(year as i32, month, day)?;
|
||||||
|
let naive_time = chrono::NaiveTime::from_hms_opt(hour, min, sec)?;
|
||||||
|
let naive_dt = chrono::NaiveDateTime::new(naive_date, naive_time);
|
||||||
|
|
||||||
|
Some(naive_dt.and_utc().timestamp() as f64)
|
||||||
|
}
|
||||||
|
|||||||
+7
-3
@@ -1,6 +1,9 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
build: ./backend
|
build: ./backend
|
||||||
|
container_name: astronome-backend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
@@ -8,13 +11,14 @@ services:
|
|||||||
- DATABASE_URL=sqlite:///data/astronome.db
|
- DATABASE_URL=sqlite:///data/astronome.db
|
||||||
- RUST_LOG=info
|
- RUST_LOG=info
|
||||||
ports:
|
ports:
|
||||||
- "3301:3301"
|
- 3001:3001
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build: ./frontend
|
build: ./frontend
|
||||||
|
container_name: astronome-frontend
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "3300:80"
|
- 3000:80
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
@@ -24,7 +28,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "80:80"
|
- "80:80"
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx-conf.conf:/etc/nginx/nginx-conf.conf:ro
|
- ./nginx-config.conf:/etc/nginx/nginx-config.conf:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
- frontend
|
- frontend
|
||||||
@@ -1,15 +1,24 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Cache static assets
|
location = /index.html {
|
||||||
|
expires -1;
|
||||||
|
add_header Cache-Control "no-cache";
|
||||||
|
}
|
||||||
|
|
||||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
|
||||||
expires 1y;
|
expires 1y;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_header X-Content-Type-Options nosniff;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,20 @@ import { useRef, useState } from 'react';
|
|||||||
import { api } from '../../api';
|
import { api } from '../../api';
|
||||||
import { useQueryClient } from '@tanstack/react-query';
|
import { useQueryClient } from '@tanstack/react-query';
|
||||||
|
|
||||||
|
interface UploadResult {
|
||||||
|
filename: string;
|
||||||
|
rms_total: number;
|
||||||
|
rms_ra: number;
|
||||||
|
rms_dec: number;
|
||||||
|
duration_min?: number;
|
||||||
|
camera_name?: string;
|
||||||
|
exposure_ms?: number;
|
||||||
|
mount_name?: string;
|
||||||
|
session_date?: string;
|
||||||
|
error?: string;
|
||||||
|
duplicate?: { id: number; message: string };
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onUploaded?: (id: number) => void;
|
onUploaded?: (id: number) => void;
|
||||||
}
|
}
|
||||||
@@ -9,54 +23,64 @@ interface Props {
|
|||||||
export default function PHD2UploadZone({ onUploaded }: Props) {
|
export default function PHD2UploadZone({ onUploaded }: Props) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [results, setResults] = useState<UploadResult[]>([]);
|
||||||
const [duplicate, setDuplicate] = useState<{ id: number; message: string } | null>(null);
|
|
||||||
const [result, setResult] = useState<{
|
|
||||||
rms_total: number;
|
|
||||||
rms_ra: number;
|
|
||||||
rms_dec: number;
|
|
||||||
duration_min?: number;
|
|
||||||
camera_name?: string;
|
|
||||||
exposure_ms?: number;
|
|
||||||
mount_name?: string;
|
|
||||||
session_date?: string;
|
|
||||||
} | null>(null);
|
|
||||||
const qc = useQueryClient();
|
const qc = useQueryClient();
|
||||||
|
|
||||||
const handleFile = async (file: File) => {
|
const handleFiles = async (files: FileList | null) => {
|
||||||
|
if (!files || files.length === 0) return;
|
||||||
|
|
||||||
setUploading(true);
|
setUploading(true);
|
||||||
setError(null);
|
setResults([]);
|
||||||
setDuplicate(null);
|
|
||||||
setResult(null);
|
|
||||||
const fd = new FormData();
|
|
||||||
fd.append('file', file);
|
|
||||||
|
|
||||||
try {
|
const uploadedIds: number[] = [];
|
||||||
const res = await api.phd2.upload(fd);
|
|
||||||
|
|
||||||
if (res.duplicate) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
setDuplicate({
|
const file = files[i];
|
||||||
id: res.duplicate_id || 0,
|
const fd = new FormData();
|
||||||
message: res.message || `Duplicate session detected (ID: ${res.duplicate_id})`
|
fd.append('file', file);
|
||||||
});
|
|
||||||
setResult(null);
|
try {
|
||||||
} else {
|
const res = await api.phd2.upload(fd);
|
||||||
const analysis = res.analysis as any;
|
|
||||||
setResult({
|
if (res.duplicate) {
|
||||||
rms_total: analysis.rms_total_arcsec,
|
setResults(prev => [...prev, {
|
||||||
rms_ra: analysis.rms_ra_arcsec,
|
filename: file.name,
|
||||||
rms_dec: analysis.rms_dec_arcsec,
|
rms_total: 0,
|
||||||
duration_min: analysis.duration_min,
|
rms_ra: 0,
|
||||||
camera_name: analysis.camera_name,
|
rms_dec: 0,
|
||||||
exposure_ms: analysis.exposure_ms,
|
duplicate: {
|
||||||
mount_name: analysis.mount_name,
|
id: res.duplicate_id || 0,
|
||||||
});
|
message: res.message || `Duplicate session detected (ID: ${res.duplicate_id})`
|
||||||
qc.invalidateQueries({ queryKey: ['phd2'] });
|
}
|
||||||
onUploaded?.(res.id);
|
}]);
|
||||||
|
} else {
|
||||||
|
const analysis = res.analysis as any;
|
||||||
|
setResults(prev => [...prev, {
|
||||||
|
filename: file.name,
|
||||||
|
rms_total: analysis.rms_total_arcsec,
|
||||||
|
rms_ra: analysis.rms_ra_arcsec,
|
||||||
|
rms_dec: analysis.rms_dec_arcsec,
|
||||||
|
duration_min: analysis.duration_min,
|
||||||
|
camera_name: analysis.camera_name,
|
||||||
|
exposure_ms: analysis.exposure_ms,
|
||||||
|
mount_name: analysis.mount_name,
|
||||||
|
session_date: analysis.session_date,
|
||||||
|
}]);
|
||||||
|
uploadedIds.push(res.id);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
setResults(prev => [...prev, {
|
||||||
|
filename: file.name,
|
||||||
|
rms_total: 0,
|
||||||
|
rms_ra: 0,
|
||||||
|
rms_dec: 0,
|
||||||
|
error: `Parse failed: ${e instanceof Error ? e.message : 'Unknown error'}`
|
||||||
|
}]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
setError(`Parse failed: ${e instanceof Error ? e.message : 'Unknown error'}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qc.invalidateQueries({ queryKey: ['phd2'] });
|
||||||
|
uploadedIds.forEach(id => onUploaded?.(id));
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -75,39 +99,54 @@ export default function PHD2UploadZone({ onUploaded }: Props) {
|
|||||||
background: 'var(--bg-deep)',
|
background: 'var(--bg-deep)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{uploading ? 'Parsing PHD2 log...' : '↑ Upload PHD2 log (.log)'}
|
{uploading ? 'Parsing PHD2 logs...' : '↑ Upload PHD2 log(s) (.log)'}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
type="file"
|
type="file"
|
||||||
accept=".log,.csv"
|
accept=".log,.csv"
|
||||||
|
multiple
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
onChange={e => e.target.files?.[0] && handleFile(e.target.files[0])}
|
onChange={e => handleFiles(e.target.files)}
|
||||||
/>
|
/>
|
||||||
{error && <div style={{ color: 'var(--danger)', fontSize: 11, marginTop: 4 }}>{error}</div>}
|
{results.map((result, idx) => (
|
||||||
{duplicate && (
|
<div key={idx} style={{ marginTop: 8, paddingTop: 8, borderTop: '1px solid var(--border)' }}>
|
||||||
<div style={{ color: 'var(--warn)', fontSize: 11, marginTop: 4, fontFamily: 'var(--font-mono)' }}>
|
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-lo)', marginBottom: 4 }}>
|
||||||
⚠ {duplicate.message}
|
{result.filename}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{result && (
|
{result.error && (
|
||||||
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--good)', marginTop: 4, lineHeight: '1.5' }}>
|
<div style={{ color: 'var(--danger)', fontSize: 11 }}>
|
||||||
<div>✓ RMS Total: {result.rms_total.toFixed(2)}″ (RA: {result.rms_ra.toFixed(2)}″ Dec: {result.rms_dec.toFixed(2)}″)</div>
|
✗ {result.error}
|
||||||
{result.session_date && (
|
</div>
|
||||||
<div style={{ color: 'var(--text-mid)', marginTop: 4 }}>Date: {result.session_date}</div>
|
|
||||||
)}
|
)}
|
||||||
{result.duration_min !== undefined && (
|
|
||||||
<div style={{ color: 'var(--text-mid)', marginTop: result.session_date ? 2 : 6 }}>Duration: {result.duration_min}m</div>
|
{result.duplicate && (
|
||||||
|
<div style={{ color: 'var(--warn)', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
|
||||||
|
⚠ {result.duplicate.message}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{(result.camera_name || result.mount_name) && (
|
|
||||||
<div style={{ color: 'var(--text-lo)', marginTop: 4 }}>
|
{!result.error && !result.duplicate && (
|
||||||
{result.camera_name && <div>Camera: {result.camera_name}</div>}
|
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--good)', lineHeight: '1.5' }}>
|
||||||
{result.mount_name && <div>Mount: {result.mount_name}</div>}
|
<div>✓ RMS: {result.rms_total.toFixed(2)}″ (RA: {result.rms_ra.toFixed(2)}″ Dec: {result.rms_dec.toFixed(2)}″)</div>
|
||||||
{result.exposure_ms && <div>Exposure: {result.exposure_ms}ms</div>}
|
{result.session_date && (
|
||||||
|
<div style={{ color: 'var(--text-mid)', marginTop: 2 }}>Date: {result.session_date}</div>
|
||||||
|
)}
|
||||||
|
{result.duration_min !== undefined && (
|
||||||
|
<div style={{ color: 'var(--text-mid)', marginTop: 2 }}>Duration: {result.duration_min}m</div>
|
||||||
|
)}
|
||||||
|
{(result.camera_name || result.mount_name) && (
|
||||||
|
<div style={{ color: 'var(--text-lo)', marginTop: 2, fontSize: 10 }}>
|
||||||
|
{result.camera_name && <div>Camera: {result.camera_name}</div>}
|
||||||
|
{result.mount_name && <div>Mount: {result.mount_name}</div>}
|
||||||
|
{result.exposure_ms && <div>Exposure: {result.exposure_ms}ms</div>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default defineConfig({
|
|||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'http://localhost:3301',
|
target: 'http://localhost:3001',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,17 +3,23 @@ events {
|
|||||||
}
|
}
|
||||||
|
|
||||||
http {
|
http {
|
||||||
upstream backend { server backend:3301; }
|
upstream backend { server backend:3001; }
|
||||||
upstream frontend { server frontend:80; }
|
upstream frontend { server frontend:80; }
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
client_max_body_size 60M;
|
client_max_body_size 60M;
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://backend;
|
proxy_pass http://backend;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
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;
|
||||||
|
|
||||||
|
proxy_connect_timeout 10s;
|
||||||
proxy_read_timeout 60s;
|
proxy_read_timeout 60s;
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user