remove nginx

This commit is contained in:
2026-04-10 00:25:43 +02:00
parent 9223e4d35f
commit 94527be868
16 changed files with 259 additions and 126 deletions
+3 -3
View File
@@ -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 {
+2
View File
@@ -0,0 +1,2 @@
# gitea token
4fe62748b43ca89f8bb0472b810aac49fa1fac8d
+1 -1
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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};
+1 -2
View File
@@ -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;
+2 -2
View File
@@ -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 {
+1 -1
View File
@@ -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
View File
@@ -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?;
+71 -1
View File
@@ -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
}; };
// Try to extract duration from guiding session timestamps first
// If not available, fall back to CSV time span
let duration_min = extract_guiding_duration(content)
.unwrap_or_else(|| {
let duration_sec = match (first_time, last_time) { let duration_sec = match (first_time, last_time) {
(Some(f), Some(l)) => (l - f).max(0.0), (Some(f), Some(l)) => (l - f).max(0.0),
_ => 0.0, _ => 0.0,
}; };
let duration_min = (duration_sec / 60.0) as u32; (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
View File
@@ -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
+9 -1
View File
@@ -1,15 +1,23 @@
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 ~* \.html$ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
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;
} }
+74 -35
View File
@@ -2,16 +2,8 @@ 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 Props { interface UploadResult {
onUploaded?: (id: number) => void; filename: string;
}
export default function PHD2UploadZone({ onUploaded }: Props) {
const inputRef = useRef<HTMLInputElement>(null);
const [uploading, setUploading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [duplicate, setDuplicate] = useState<{ id: number; message: string } | null>(null);
const [result, setResult] = useState<{
rms_total: number; rms_total: number;
rms_ra: number; rms_ra: number;
rms_dec: number; rms_dec: number;
@@ -20,14 +12,30 @@ export default function PHD2UploadZone({ onUploaded }: Props) {
exposure_ms?: number; exposure_ms?: number;
mount_name?: string; mount_name?: string;
session_date?: string; session_date?: string;
} | null>(null); error?: string;
duplicate?: { id: number; message: string };
}
interface Props {
onUploaded?: (id: number) => void;
}
export default function PHD2UploadZone({ onUploaded }: Props) {
const inputRef = useRef<HTMLInputElement>(null);
const [uploading, setUploading] = useState(false);
const [results, setResults] = useState<UploadResult[]>([]);
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 uploadedIds: number[] = [];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const fd = new FormData(); const fd = new FormData();
fd.append('file', file); fd.append('file', file);
@@ -35,14 +43,20 @@ export default function PHD2UploadZone({ onUploaded }: Props) {
const res = await api.phd2.upload(fd); const res = await api.phd2.upload(fd);
if (res.duplicate) { if (res.duplicate) {
setDuplicate({ setResults(prev => [...prev, {
filename: file.name,
rms_total: 0,
rms_ra: 0,
rms_dec: 0,
duplicate: {
id: res.duplicate_id || 0, id: res.duplicate_id || 0,
message: res.message || `Duplicate session detected (ID: ${res.duplicate_id})` message: res.message || `Duplicate session detected (ID: ${res.duplicate_id})`
}); }
setResult(null); }]);
} else { } else {
const analysis = res.analysis as any; const analysis = res.analysis as any;
setResult({ setResults(prev => [...prev, {
filename: file.name,
rms_total: analysis.rms_total_arcsec, rms_total: analysis.rms_total_arcsec,
rms_ra: analysis.rms_ra_arcsec, rms_ra: analysis.rms_ra_arcsec,
rms_dec: analysis.rms_dec_arcsec, rms_dec: analysis.rms_dec_arcsec,
@@ -50,13 +64,23 @@ export default function PHD2UploadZone({ onUploaded }: Props) {
camera_name: analysis.camera_name, camera_name: analysis.camera_name,
exposure_ms: analysis.exposure_ms, exposure_ms: analysis.exposure_ms,
mount_name: analysis.mount_name, mount_name: analysis.mount_name,
}); session_date: analysis.session_date,
qc.invalidateQueries({ queryKey: ['phd2'] }); }]);
onUploaded?.(res.id); uploadedIds.push(res.id);
} }
} catch (e) { } catch (e) {
setError(`Parse failed: ${e instanceof Error ? e.message : 'Unknown error'}`); 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'}`
}]);
} }
}
qc.invalidateQueries({ queryKey: ['phd2'] });
uploadedIds.forEach(id => onUploaded?.(id));
setUploading(false); setUploading(false);
}; };
@@ -75,32 +99,45 @@ 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>
{result.error && (
<div style={{ color: 'var(--danger)', fontSize: 11 }}>
{result.error}
</div> </div>
)} )}
{result && (
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--good)', marginTop: 4, lineHeight: '1.5' }}> {result.duplicate && (
<div> RMS Total: {result.rms_total.toFixed(2)} (RA: {result.rms_ra.toFixed(2)} Dec: {result.rms_dec.toFixed(2)})</div> <div style={{ color: 'var(--warn)', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
{result.duplicate.message}
</div>
)}
{!result.error && !result.duplicate && (
<div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--good)', lineHeight: '1.5' }}>
<div> RMS: {result.rms_total.toFixed(2)} (RA: {result.rms_ra.toFixed(2)} Dec: {result.rms_dec.toFixed(2)})</div>
{result.session_date && ( {result.session_date && (
<div style={{ color: 'var(--text-mid)', marginTop: 4 }}>Date: {result.session_date}</div> <div style={{ color: 'var(--text-mid)', marginTop: 2 }}>Date: {result.session_date}</div>
)} )}
{result.duration_min !== undefined && ( {result.duration_min !== undefined && (
<div style={{ color: 'var(--text-mid)', marginTop: result.session_date ? 2 : 6 }}>Duration: {result.duration_min}m</div> <div style={{ color: 'var(--text-mid)', marginTop: 2 }}>Duration: {result.duration_min}m</div>
)} )}
{(result.camera_name || result.mount_name) && ( {(result.camera_name || result.mount_name) && (
<div style={{ color: 'var(--text-lo)', marginTop: 4 }}> <div style={{ color: 'var(--text-lo)', marginTop: 2, fontSize: 10 }}>
{result.camera_name && <div>Camera: {result.camera_name}</div>} {result.camera_name && <div>Camera: {result.camera_name}</div>}
{result.mount_name && <div>Mount: {result.mount_name}</div>} {result.mount_name && <div>Mount: {result.mount_name}</div>}
{result.exposure_ms && <div>Exposure: {result.exposure_ms}ms</div>} {result.exposure_ms && <div>Exposure: {result.exposure_ms}ms</div>}
@@ -109,5 +146,7 @@ export default function PHD2UploadZone({ onUploaded }: Props) {
</div> </div>
)} )}
</div> </div>
))}
</div>
); );
} }
+1 -1
View File
@@ -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,
}, },
}, },
-25
View File
@@ -1,25 +0,0 @@
events {
worker_connections 1024;
}
http {
upstream backend { server backend:3301; }
upstream frontend { server frontend:80; }
server {
listen 80;
client_max_body_size 60M;
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 60s;
}
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
}
}
}
+39
View File
@@ -0,0 +1,39 @@
events {
worker_connections 1024;
}
http {
upstream backend { server backend:3001; }
upstream frontend { server frontend:80; }
server {
listen 80;
server_name _;
client_max_body_size 60M;
location /api/ {
proxy_pass http://backend;
proxy_set_header Host $host;
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;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
proxy_set_header If-Modified-Since "";
proxy_set_header If-None-Match "";
}
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
}
}
}