Fixed docker issue temporarily

This commit is contained in:
2025-04-11 23:02:47 -04:00
parent 05b5ceafe2
commit 74fa7da751
18 changed files with 145 additions and 92 deletions

10
.env
View File

@@ -2,8 +2,7 @@ RUST_LOG=warn,api=info
HTTPD_DOMAIN=localhost HTTPD_DOMAIN=localhost
HTTPD_PROTOCOL=http HTTPD_PROTOCOL=http
HTTPD_HTTP_PORT=8080 HTTPD_PORT=8080
HTTPD_HTTPS_PORT=8443
HTTPD_MINIO_HOST=host.docker.internal HTTPD_MINIO_HOST=host.docker.internal
HTTPD_API_HOST=host.docker.internal HTTPD_API_HOST=host.docker.internal
HTTPD_UI_HOST=host.docker.internal HTTPD_UI_HOST=host.docker.internal
@@ -24,18 +23,19 @@ MINIO_BUCKET=aviation
MINIO_PROTOCOL=http MINIO_PROTOCOL=http
MINIO_PORT=9000 MINIO_PORT=9000
MINIO_PORT_INTERNAL=9001 MINIO_PORT_INTERNAL=9001
MINIO_BROWSER_REDIRECT_URL=${HTTPD_PROTOCOL}://${HTTPD_DOMAIN}:${HTTPD_HTTP_PORT}/minio/ MINIO_BROWSER_REDIRECT_URL=${HTTPD_PROTOCOL}://${HTTPD_DOMAIN}:${HTTPD_PORT}/minio/
UI_PROTOCOL=http UI_PROTOCOL=http
UI_PORT=3000 UI_PORT=3000
API_PROTOCOL=http API_PROTOCOL=http
API_HOST=0.0.0.0
API_PORT=5000 API_PORT=5000
VITE_API_URL=${HTTPD_PROTOCOL}://${HTTPD_DOMAIN}:${HTTPD_HTTP_PORT}/api VITE_API_URL=${HTTPD_PROTOCOL}://${HTTPD_DOMAIN}:${HTTPD_PORT}/api
ENVIRONMENT=development ENVIRONMENT=development
ADMIN_EMAIL=admin@example.com ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=CHANGEME ADMIN_PASSWORD=CHANGEME
GOV_API_URL=https://aviationweather.gov/cgi-bin/data AVIATION_WEATHER_URL=https://aviationweather.gov/api/data

View File

@@ -11,26 +11,14 @@ COPY Cargo.toml ./
RUN apt-get update && apt-get install -y cmake RUN apt-get update && apt-get install -y cmake
RUN cargo build --release RUN cargo build --release
# ======
# Keys
# ======
FROM debian:bookworm-slim AS keys
WORKDIR /keys
RUN apt-get update && apt-get install -y openssl libpq-dev
RUN openssl genrsa -out access.pem 4096
RUN openssl rsa -in access.pem -pubout -outform PEM -out access.pem.pub
RUN openssl genrsa -out refresh.pem 4096
RUN openssl rsa -in refresh.pem -pubout -outform PEM -out refresh.pem.pub
# ========= # =========
# Runtime # Runtime
# ========= # =========
FROM keys AS runtime FROM debian:bookworm-slim AS runtime
WORKDIR /api WORKDIR /api
RUN apt-get update && apt-get install -y openssl libpq-dev
USER root USER root
COPY --from=builder /builder/target/release/api /usr/local/bin/api COPY --from=builder /builder/target/release/api /usr/local/bin/api
COPY --from=keys /keys /keys
CMD ["api"] CMD ["api"]

View File

@@ -17,6 +17,15 @@ CREATE TABLE IF NOT EXISTS airports (
public BOOLEAN DEFAULT false public BOOLEAN DEFAULT false
); );
CREATE INDEX ON airports (iata);
CREATE INDEX ON airports (local);
CREATE INDEX ON airports (name);
CREATE INDEX ON airports (category);
CREATE INDEX ON airports (iso_country);
CREATE INDEX ON airports (iso_region);
CREATE INDEX ON airports (municipality);
CREATE INDEX ON airports (longitude, latitude);
CREATE TABLE IF NOT EXISTS runways ( CREATE TABLE IF NOT EXISTS runways (
id UUID PRIMARY KEY NOT NULL, id UUID PRIMARY KEY NOT NULL,
icao TEXT NOT NULL, icao TEXT NOT NULL,
@@ -26,6 +35,9 @@ CREATE TABLE IF NOT EXISTS runways (
surface TEXT NOT NULL surface TEXT NOT NULL
); );
CREATE INDEX ON runways (icao);
CREATE INDEX ON runways (surface);
CREATE TABLE IF NOT EXISTS frequencies ( CREATE TABLE IF NOT EXISTS frequencies (
id UUID PRIMARY KEY NOT NULL, id UUID PRIMARY KEY NOT NULL,
icao TEXT NOT NULL, icao TEXT NOT NULL,
@@ -33,6 +45,9 @@ CREATE TABLE IF NOT EXISTS frequencies (
frequency_mhz REAL NOT NULL frequency_mhz REAL NOT NULL
); );
CREATE INDEX ON frequencies (icao);
CREATE INDEX ON frequencies (frequency_mhz);
CREATE TABLE IF NOT EXISTS metars ( CREATE TABLE IF NOT EXISTS metars (
icao TEXT NOT NULL, icao TEXT NOT NULL,
observation_time TIMESTAMPTZ NOT NULL, observation_time TIMESTAMPTZ NOT NULL,
@@ -40,6 +55,8 @@ CREATE TABLE IF NOT EXISTS metars (
data JSONB NOT NULL data JSONB NOT NULL
); );
CREATE INDEX ON metars (observation_time DESC);
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
email TEXT PRIMARY KEY NOT NULL, email TEXT PRIMARY KEY NOT NULL,
password_hash TEXT NOT NULL, password_hash TEXT NOT NULL,

View File

@@ -1,6 +1,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::str::FromStr; use std::str::FromStr;
use futures_util::try_join; use futures_util::try_join;
use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::{Postgres, QueryBuilder}; use sqlx::{Postgres, QueryBuilder};
use crate::airports::{ use crate::airports::{
@@ -194,7 +195,7 @@ impl From<AirportRow> for Airport {
} }
impl Airport { impl Airport {
pub async fn select(icao: &str, metar: bool) -> Option<Self> { pub async fn select(client: &Client, icao: &str, metar: bool) -> Option<Self> {
let pool = db::pool(); let pool = db::pool();
let airport_fut = async { let airport_fut = async {
@@ -206,7 +207,7 @@ impl Airport {
let metar_fut = async { let metar_fut = async {
if metar { if metar {
match Metar::find_all(&vec![icao.to_string()]).await { match Metar::find_all(client, &vec![icao.to_string()], &false).await {
Ok(m) => Some(m.into_iter().nth(0)), Ok(m) => Some(m.into_iter().nth(0)),
Err(err) => { Err(err) => {
log::error!("{}", err); log::error!("{}", err);
@@ -269,7 +270,7 @@ impl Airport {
}) })
} }
pub async fn select_all(query: &AirportQuery) -> ApiResult<Vec<Self>> { pub async fn select_all(client: &Client, query: &AirportQuery) -> ApiResult<Vec<Self>> {
let pool = db::pool(); let pool = db::pool();
let mut builder = QueryBuilder::<Postgres>::new("SELECT * FROM "); let mut builder = QueryBuilder::<Postgres>::new("SELECT * FROM ");
@@ -337,7 +338,7 @@ impl Airport {
let runway_future = Runway::select_all_map(icaos.clone()); let runway_future = Runway::select_all_map(icaos.clone());
let frequency_future = Frequency::select_all_map(icaos.clone()); let frequency_future = Frequency::select_all_map(icaos.clone());
let metar_future = if query.metars.unwrap_or(false) { let metar_future = if query.metars.unwrap_or(false) {
Some(Metar::find_all(&icaos)) Some(Metar::find_all(client, &icaos, &false))
} else { } else {
None None
}; };

View File

@@ -4,6 +4,7 @@ use crate::{
airports::Airport, airports::Airport,
db::Paged, db::Paged,
auth::{Auth, verify_role}, auth::{Auth, verify_role},
AppState,
}; };
use actix_multipart::Multipart; use actix_multipart::Multipart;
use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest, ResponseError}; use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest, ResponseError};
@@ -53,7 +54,7 @@ async fn import_airports(mut payload: Multipart, auth: Auth) -> HttpResponse {
} }
#[get("")] #[get("")]
async fn get_airports(req: HttpRequest) -> HttpResponse { async fn get_airports(data: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
let mut query = match web::Query::<AirportQuery>::from_query(req.query_string()) { let mut query = match web::Query::<AirportQuery>::from_query(req.query_string()) {
Ok(q) => q.into_inner(), Ok(q) => q.into_inner(),
Err(err) => { Err(err) => {
@@ -71,7 +72,8 @@ async fn get_airports(req: HttpRequest) -> HttpResponse {
query.limit = Some(limit); query.limit = Some(limit);
query.page = Some(page); query.page = Some(page);
match Airport::select_all(&query).await { let client = &data.client;
match Airport::select_all(client, &query).await {
Ok(airports) => HttpResponse::Ok().json(Paged { Ok(airports) => HttpResponse::Ok().json(Paged {
data: airports, data: airports,
page, page,
@@ -86,7 +88,11 @@ async fn get_airports(req: HttpRequest) -> HttpResponse {
} }
#[get("/{icao}")] #[get("/{icao}")]
async fn get_airport(icao: web::Path<String>, req: HttpRequest) -> HttpResponse { async fn get_airport(
data: web::Data<AppState>,
icao: web::Path<String>,
req: HttpRequest,
) -> HttpResponse {
let metar = match web::Query::<AirportQuery>::from_query(req.query_string()) { let metar = match web::Query::<AirportQuery>::from_query(req.query_string()) {
Ok(q) => q.metars.unwrap_or_else(|| false), Ok(q) => q.metars.unwrap_or_else(|| false),
Err(err) => { Err(err) => {
@@ -95,7 +101,8 @@ async fn get_airport(icao: web::Path<String>, req: HttpRequest) -> HttpResponse
} }
}; };
match Airport::select(&icao.into_inner(), metar).await { let client = &data.client;
match Airport::select(client, &icao.into_inner(), metar).await {
Some(airport) => HttpResponse::Ok().json(airport), Some(airport) => HttpResponse::Ok().json(airport),
None => HttpResponse::NotFound().finish(), None => HttpResponse::NotFound().finish(),
} }

View File

@@ -97,7 +97,18 @@ impl From<std::env::VarError> for Error {
impl From<reqwest::Error> for Error { impl From<reqwest::Error> for Error {
fn from(error: reqwest::Error) -> Self { fn from(error: reqwest::Error) -> Self {
Self::new(500, format!("Unknown reqwest error: {}", error)) match error.status() {
Some(status_code) => {
if status_code.is_client_error() {
Self::new(500, format!("Client reqwest error: {}", error))
} else if status_code.is_server_error() {
Self::new(500, format!("Server reqwest error: {}", error))
} else {
Self::new(500, format!("Unknown reqwest error: {:?}", error))
}
}
_ => Self::new(500, format!("Unknown reqwest error: {:?}", error)),
}
} }
} }

View File

@@ -1,5 +1,5 @@
use std::env; use std::env;
use std::time::Duration;
use actix_cors::Cors; use actix_cors::Cors;
use actix_web::{App, HttpServer, middleware::Logger, web}; use actix_web::{App, HttpServer, middleware::Logger, web};
use dotenv::from_filename; use dotenv::from_filename;
@@ -14,15 +14,17 @@ mod metars;
mod scheduler; mod scheduler;
mod users; mod users;
#[derive(Debug, Clone)]
struct AppState {
client: reqwest::Client,
}
#[actix_web::main] #[actix_web::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> { async fn main() -> Result<(), Box<dyn std::error::Error>> {
initialize_environment()?; initialize_environment()?;
db::initialize().await?; db::initialize().await?;
// scheduler::update_airports(); // scheduler::update_airports();
let host = "0.0.0.0".to_string();
let port = env::var("API_PORT").unwrap_or("5000".to_string());
// Initialize admin user // Initialize admin user
let admin_email = env::var("ADMIN_EMAIL"); let admin_email = env::var("ADMIN_EMAIL");
let admin_password = env::var("ADMIN_PASSWORD"); let admin_password = env::var("ADMIN_PASSWORD");
@@ -55,6 +57,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
} }
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(10))
.no_proxy()
.danger_accept_invalid_certs(true)
.build()
.expect("Failed to create reqwest client");
let state = AppState { client };
let host = env::var("API_HOST").unwrap_or("localhost".to_string());
let port = env::var("API_PORT").unwrap_or("5000".to_string());
let server = match HttpServer::new(move || { let server = match HttpServer::new(move || {
let cors = Cors::default() let cors = Cors::default()
.allow_any_origin() .allow_any_origin()
@@ -62,18 +75,22 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.allow_any_header() .allow_any_header()
.supports_credentials() .supports_credentials()
.max_age(3600); .max_age(3600);
App::new().wrap(cors).wrap(Logger::default()).service( App::new()
web::scope("api") .wrap(cors)
.configure(airports::init_routes) .wrap(Logger::default())
.configure(metars::init_routes) .app_data(web::Data::new(state.clone()))
.configure(auth::init_routes) .service(
.configure(users::init_routes), web::scope("api")
) .configure(airports::init_routes)
.configure(metars::init_routes)
.configure(auth::init_routes)
.configure(users::init_routes),
)
}) })
.bind(format!("{}:{}", host, port)) .bind(format!("{}:{}", host, port))
{ {
Ok(b) => { Ok(b) => {
log::info!("Binding server to {}:{}", host, port); log::info!("Server bound to {}:{}", host, port);
b b
} }
Err(err) => { Err(err) => {

View File

@@ -3,6 +3,7 @@ use crate::{error::ApiResult, db};
use chrono::{DateTime, Datelike, Utc}; use chrono::{DateTime, Datelike, Utc};
use std::collections::HashSet; use std::collections::HashSet;
use redis::{AsyncCommands, RedisResult}; use redis::{AsyncCommands, RedisResult};
use reqwest::Client;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::db::redis_async_connection; use crate::db::redis_async_connection;
@@ -845,8 +846,8 @@ impl Metar {
missing_metar_icaos missing_metar_icaos
} }
async fn get_remote_metars(icaos: &[&str]) -> ApiResult<Vec<Metar>> { async fn get_remote_metars(client: &Client, icaos: &[&str]) -> ApiResult<Vec<Metar>> {
let gov_api_url = std::env::var("GOV_API_URL").expect("GOV_API_URL must be set"); let base_url = std::env::var("AVIATION_WEATHER_URL").expect("GOV_API_URL must be set");
// Query the remote API for the missing METAR data 10 at a time // Query the remote API for the missing METAR data 10 at a time
let icao_chunks = icaos let icao_chunks = icaos
.chunks(10) .chunks(10)
@@ -854,14 +855,14 @@ impl Metar {
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let mut metars: Vec<Metar> = vec![]; let mut metars: Vec<Metar> = vec![];
for icao_chunk in icao_chunks { for icao_chunk in icao_chunks {
let url = format!("{}/metar.php?ids={}", gov_api_url, icao_chunk); let url = format!("{}/metar?ids={}&order=id", base_url, icao_chunk);
let mut m = match reqwest::get(url).await { let mut m = match client.get(url).send().await {
Ok(r) => { Ok(r) => {
// Check if the status code is 200 // Check if the status code is 200
if r.status() != 200 { if r.status() != 200 {
return Err(Error::new( return Err(Error::new(
500, 500,
format!("Unable to get METAR request: {}", r.status()), format!("Request returned status {}", r.status()),
)); ));
} }
match r.text().await { match r.text().await {
@@ -876,20 +877,10 @@ impl Metar {
Err(err) => return Err(err), Err(err) => return Err(err),
} }
} }
Err(err) => { Err(err) => return Err(Error::new(500, format!("METAR parse failed: {}", err))),
return Err(Error::new(
500,
format!("Unable to parse METAR request: {}", err),
))
}
} }
} }
Err(err) => { Err(err) => return Err(err.into()),
return Err(Error::new(
500,
format!("Unable to get METAR request: {}", err),
))
}
}; };
metars.append(&mut m); metars.append(&mut m);
} }
@@ -911,7 +902,11 @@ impl Metar {
}) })
} }
pub async fn find_all(icao_list: &Vec<String>) -> ApiResult<Vec<Self>> { pub async fn find_all(
client: &Client,
icao_list: &Vec<String>,
force: &bool,
) -> ApiResult<Vec<Self>> {
if icao_list.is_empty() { if icao_list.is_empty() {
return Ok(Vec::new()); return Ok(Vec::new());
} }
@@ -937,17 +932,21 @@ impl Metar {
if !missing_icao_list.is_empty() { if !missing_icao_list.is_empty() {
let mut updated_missing_icao_list: Vec<&str> = Vec::new(); let mut updated_missing_icao_list: Vec<&str> = Vec::new();
for icao in &missing_icao_list { for icao in &missing_icao_list {
let result: RedisResult<Option<bool>> = conn.get(icao).await; if *force {
match result { updated_missing_icao_list.push(icao);
Ok(Some(value)) => { } else {
if value { let result: RedisResult<Option<bool>> = conn.get(icao).await;
match result {
Ok(Some(value)) => {
if value {
updated_missing_icao_list.push(icao);
}
}
Ok(None) => {
updated_missing_icao_list.push(icao); updated_missing_icao_list.push(icao);
} }
Err(err) => return Err(err.into()),
} }
Ok(None) => {
updated_missing_icao_list.push(icao);
}
Err(err) => return Err(err.into()),
} }
} }
if !updated_missing_icao_list.is_empty() { if !updated_missing_icao_list.is_empty() {
@@ -955,7 +954,7 @@ impl Metar {
"Retrieving missing METAR data for {:?}", "Retrieving missing METAR data for {:?}",
updated_missing_icao_list updated_missing_icao_list
); );
let mut missing_icao_list = Self::get_remote_metars(&updated_missing_icao_list) let mut missing_icao_list = Self::get_remote_metars(client, &updated_missing_icao_list)
.await .await
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
log::warn!("Unable to get remote METAR data; {}", err); log::warn!("Unable to get remote METAR data; {}", err);

View File

@@ -2,14 +2,16 @@ use crate::metars::Metar;
use actix_web::{get, web, HttpResponse, HttpRequest}; use actix_web::{get, web, HttpResponse, HttpRequest};
use log::error; use log::error;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::AppState;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct FindAllParameters { struct FindAllParameters {
icaos: Option<String>, icaos: Option<String>,
force: Option<bool>,
} }
#[get("metars")] #[get("metars")]
async fn find_all(req: HttpRequest) -> HttpResponse { async fn find_all(data: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
let parameters = web::Query::<FindAllParameters>::from_query(req.query_string()).unwrap(); let parameters = web::Query::<FindAllParameters>::from_query(req.query_string()).unwrap();
let icao_option = &parameters.icaos; let icao_option = &parameters.icaos;
let icao_string = match icao_option { let icao_string = match icao_option {
@@ -17,8 +19,10 @@ async fn find_all(req: HttpRequest) -> HttpResponse {
None => return HttpResponse::UnprocessableEntity().body("Missing icaos parameter"), None => return HttpResponse::UnprocessableEntity().body("Missing icaos parameter"),
}; };
let icaos: Vec<String> = icao_string.split(',').map(|s| s.to_string()).collect(); let icaos: Vec<String> = icao_string.split(',').map(|s| s.to_string()).collect();
let force = &parameters.force.unwrap_or(false);
let metars = match Metar::find_all(&icaos).await { let client = &data.client;
let metars = match Metar::find_all(client, &icaos, force).await {
Ok(a) => a, Ok(a) => a,
Err(err) => { Err(err) => {
error!("{}", err); error!("{}", err);

View File

@@ -5,11 +5,12 @@ meta {
} }
get { get {
url: {{API_URL}}/metars?icaos=KJYO,KOKV,KMRB,KHEF,KIAD url: {{API_URL}}/metars?icaos=KJYO,KOKV,KMRB,KHEF,KIAD&force=true
body: none body: none
auth: none auth: none
} }
params:query { params:query {
icaos: KJYO,KOKV,KMRB,KHEF,KIAD icaos: KJYO,KOKV,KMRB,KHEF,KIAD
force: true
} }

View File

@@ -1,3 +1,6 @@
vars { vars {
BASE_URL: http://localhost:8080 BASE_URL: http://localhost:8080
~BASE_URL: http://localhost:5000
~BASE_URL: http://127.0.0.1:5000
~BASE_URL: http://[::1]:5000
} }

View File

@@ -4,6 +4,9 @@ x-env_file: &env
- path: .env.local - path: .env.local
required: false required: false
x-restart: &default_restart
restart: unless-stopped
name: aviation name: aviation
services: services:
httpd: httpd:
@@ -14,12 +17,11 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
env_file: *env env_file: *env
ports: ports:
- "${HTTPD_HTTP_PORT:-8080}:80" - "${HTTPD_PORT:-8080}:80"
- "${HTTPD_HTTPS_PORT:-8443}:443"
networks: networks:
- frontend - frontend
- backend - backend
restart: unless-stopped <<: *default_restart
postgres: postgres:
image: postgis/postgis:17-3.5 image: postgis/postgis:17-3.5
@@ -38,7 +40,7 @@ services:
- backend - backend
profiles: profiles:
- backend - backend
restart: unless-stopped <<: *default_restart
redis: redis:
image: redis:8.0-M03 # Replace with valkey? image: redis:8.0-M03 # Replace with valkey?
@@ -56,7 +58,7 @@ services:
- backend - backend
profiles: profiles:
- backend - backend
restart: unless-stopped <<: *default_restart
minio: minio:
image: minio/minio:RELEASE.2025-02-28T09-55-16Z image: minio/minio:RELEASE.2025-02-28T09-55-16Z
@@ -76,7 +78,7 @@ services:
profiles: profiles:
- backend - backend
command: server --console-address ":9001" /data command: server --console-address ":9001" /data
restart: unless-stopped <<: *default_restart
api: api:
image: aviation-api:latest image: aviation-api:latest
@@ -86,6 +88,7 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
env_file: *env env_file: *env
environment: environment:
API_HOST: 0.0.0.0
POSTGRES_HOST: aviation-postgres POSTGRES_HOST: aviation-postgres
POSTGRES_PORT: 5432 POSTGRES_PORT: 5432
REDIS_HOST: aviation-redis REDIS_HOST: aviation-redis
@@ -103,7 +106,7 @@ services:
- backend - backend
profiles: profiles:
- api - api
restart: unless-stopped <<: *default_restart
ui: ui:
image: aviation-ui:latest image: aviation-ui:latest
@@ -125,7 +128,7 @@ services:
profiles: profiles:
- frontend - frontend
command: ["npm", "run", "dev"] command: ["npm", "run", "dev"]
restart: unless-stopped <<: *default_restart
volumes: volumes:
postgres: postgres:

View File

@@ -158,14 +158,14 @@ LoadModule proxy_http_module modules/mod_proxy_http.so
#LoadModule session_dbd_module modules/mod_session_dbd.so #LoadModule session_dbd_module modules/mod_session_dbd.so
#LoadModule slotmem_shm_module modules/mod_slotmem_shm.so #LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
#LoadModule slotmem_plain_module modules/mod_slotmem_plain.so #LoadModule slotmem_plain_module modules/mod_slotmem_plain.so
LoadModule ssl_module modules/mod_ssl.so #LoadModule ssl_module modules/mod_ssl.so
#LoadModule optional_hook_export_module modules/mod_optional_hook_export.so #LoadModule optional_hook_export_module modules/mod_optional_hook_export.so
#LoadModule optional_hook_import_module modules/mod_optional_hook_import.so #LoadModule optional_hook_import_module modules/mod_optional_hook_import.so
#LoadModule optional_fn_import_module modules/mod_optional_fn_import.so #LoadModule optional_fn_import_module modules/mod_optional_fn_import.so
#LoadModule optional_fn_export_module modules/mod_optional_fn_export.so #LoadModule optional_fn_export_module modules/mod_optional_fn_export.so
#LoadModule dialup_module modules/mod_dialup.so #LoadModule dialup_module modules/mod_dialup.so
LoadModule http2_module modules/mod_http2.so #LoadModule http2_module modules/mod_http2.so
LoadModule proxy_http2_module modules/mod_proxy_http2.so #LoadModule proxy_http2_module modules/mod_proxy_http2.so
#LoadModule md_module modules/mod_md.so #LoadModule md_module modules/mod_md.so
#LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so #LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so #LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
@@ -229,7 +229,7 @@ Group www-data
# e-mailed. This address appears on some server-generated pages, such # e-mailed. This address appears on some server-generated pages, such
# as error documents. e.g. admin@your-domain.com # as error documents. e.g. admin@your-domain.com
# #
ServerAdmin you@example.com ServerAdmin ben@bensherrif.com
# #
# ServerName gives the name and port that the server uses to identify itself. # ServerName gives the name and port that the server uses to identify itself.

View File

@@ -50,7 +50,9 @@ body,
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
transition: background-color 0.2s, color 0.2s; transition:
background-color 0.2s,
color 0.2s;
} }
.map-button.active { .map-button.active {

View File

@@ -42,14 +42,14 @@ function App() {
useEffect(() => { useEffect(() => {
if (showRadar) { if (showRadar) {
getWeatherMapUrl().then(url => { getWeatherMapUrl().then((url) => {
setRainViewerUrl(url); setRainViewerUrl(url);
}); });
} }
}, [showRadar]); }, [showRadar]);
function toggleRadar() { function toggleRadar() {
setShowRadar(prev => { setShowRadar((prev) => {
const newValue = !prev; const newValue = !prev;
Cookies.set('showRadar', newValue.toString(), { expires: 7 }); Cookies.set('showRadar', newValue.toString(), { expires: 7 });
return newValue; return newValue;
@@ -96,7 +96,7 @@ function App() {
<TileLayer url={darkLayerUrl} /> <TileLayer url={darkLayerUrl} />
</LayersControl.BaseLayer> </LayersControl.BaseLayer>
</LayersControl> </LayersControl>
{rainViewerUrl && showRadar && <TileLayer url={rainViewerUrl} opacity={0.5} zIndex={5} />} {rainViewerUrl && showRadar && <TileLayer url={rainViewerUrl} opacity={0.5} zIndex={10} />}
<ZoomControl position={'bottomright'} /> <ZoomControl position={'bottomright'} />
<AirportLayer setAirport={setAirport} /> <AirportLayer setAirport={setAirport} />
<BaseLayerChangeHandler /> <BaseLayerChangeHandler />

View File

@@ -27,7 +27,7 @@ export default function AirportMarker({
mouseout: () => markerRef.current?.closePopup() mouseout: () => markerRef.current?.closePopup()
}} }}
> >
<Popup closeButton={false}> <Popup closeButton={false} autoPan={false}>
{airport.icao} - {airport.name} {airport.icao} - {airport.name}
</Popup> </Popup>
</Marker> </Marker>

View File

@@ -4,7 +4,7 @@ const weatherMapsUrl = 'https://api.rainviewer.com/public/weather-maps.json';
async function getWeatherMaps(): Promise<WeatherMaps | undefined> { async function getWeatherMaps(): Promise<WeatherMaps | undefined> {
const response = await fetch(`${weatherMapsUrl}`, { const response = await fetch(`${weatherMapsUrl}`, {
method: 'GET', method: 'GET'
}); });
if (response?.status === 200) { if (response?.status === 200) {
return response.json(); return response.json();
@@ -18,16 +18,16 @@ export async function getWeatherMapUrl(): Promise<string | null> {
if (weatherMaps != undefined) { if (weatherMaps != undefined) {
let url = weatherMaps.host; let url = weatherMaps.host;
// url = 'https://cdn.rainviewer.com'; // url = 'https://cdn.rainviewer.com';
let latest = ""; let latest = '';
if (weatherMaps.radar.past.length > 0) { if (weatherMaps.radar.past.length > 0) {
latest = weatherMaps.radar.past[weatherMaps.radar.past.length - 1].path; latest = weatherMaps.radar.past[weatherMaps.radar.past.length - 1].path;
} else { } else {
return null; return null;
} }
url += latest + "/256/{z}/{x}/{y}/2/1_1.png"; url += latest + '/256/{z}/{x}/{y}/2/1_1.png';
// url += latest + "/256/{z}/{x}/{y}/255/1_1_1_0.webp"; // url += latest + "/256/{z}/{x}/{y}/255/1_1_1_0.webp";
return url; return url;
} else { } else {
return null; return null;
} }
} }

View File

@@ -18,4 +18,4 @@ export interface SatelliteObject {
export interface FrameObject { export interface FrameObject {
time: number; time: number;
path: string; path: string;
} }