Updated UI
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ node_modules
|
|||||||
target/
|
target/
|
||||||
dist/
|
dist/
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
ssl/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -109,3 +109,7 @@ build: ## Build a specific docker image (`make build f=httpd`)
|
|||||||
${folder}
|
${folder}
|
||||||
|
|
||||||
docker-build: build
|
docker-build: build
|
||||||
|
|
||||||
|
cert: domain=$(if $(d),$(d),aviation.bensherriff.com)
|
||||||
|
cert: ## Generate a cert for the given domain
|
||||||
|
@./scripts/generate_cert.sh ${domain}
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
ProxyPreserveHost On
|
ProxyPreserveHost On
|
||||||
LogLevel warn
|
LogLevel warn
|
||||||
|
|
||||||
|
#SSLEngine on
|
||||||
|
#SSLCertificateFile /path/to/your/cert.pem
|
||||||
|
#SSLCertificateKeyFile /path/to/your/privkey.pem
|
||||||
|
|
||||||
|
#Protocols h2 http/1.1
|
||||||
|
|
||||||
ProxyPass "/api" "${API_PROTOCOL}://${HTTPD_API_HOST}:${API_PORT}/api"
|
ProxyPass "/api" "${API_PROTOCOL}://${HTTPD_API_HOST}:${API_PORT}/api"
|
||||||
ProxyPassReverse "/api" "${API_PROTOCOL}://${HTTPD_API_HOST}:${API_PORT}/api"
|
ProxyPassReverse "/api" "${API_PROTOCOL}://${HTTPD_API_HOST}:${API_PORT}/api"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
32
scripts/generate_cert.sh
Executable file
32
scripts/generate_cert.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Usage: ./generate_cert.sh example.com
|
||||||
|
if [ "$#" -lt 1 ]; then
|
||||||
|
echo "Usage: $0 <domain>"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DOMAIN=$1
|
||||||
|
DAYS=365
|
||||||
|
SSL_DIR="./ssl"
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
mkdir -p "$SSL_DIR"
|
||||||
|
|
||||||
|
KEY_PATH="${SSL_DIR}/${DOMAIN}.key"
|
||||||
|
CRT_PATH="${SSL_DIR}/${DOMAIN}.crt"
|
||||||
|
|
||||||
|
echo "Generating self-signed certificate for ${DOMAIN}..."
|
||||||
|
|
||||||
|
openssl req -x509 -nodes -days ${DAYS} -newkey rsa:2048 \
|
||||||
|
-keyout "${KEY_PATH}" \
|
||||||
|
-out "${CRT_PATH}" \
|
||||||
|
-subj "/CN=${DOMAIN}"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Successfully generated certificate:"
|
||||||
|
echo "Private Key: ${KEY_PATH}"
|
||||||
|
echo "Certificate: ${CRT_PATH}"
|
||||||
|
else
|
||||||
|
echo "Certificate generation failed."
|
||||||
|
fi
|
||||||
@@ -29,3 +29,39 @@ body,
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.map-button {
|
||||||
|
position: absolute;
|
||||||
|
right: 12px;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
color: #000;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15);
|
||||||
|
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 30px; /* Vertically center text */
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background-color 0.2s, color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-button.active {
|
||||||
|
background-color: #228be6;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-button.active:hover {
|
||||||
|
background-color: #187ed7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-button:hover {
|
||||||
|
background: #e6e6e6;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MapContainer, TileLayer, ZoomControl } from 'react-leaflet';
|
import { LayersControl, MapContainer, TileLayer, useMapEvents, ZoomControl } from 'react-leaflet';
|
||||||
import '@mantine/core/styles.css';
|
import '@mantine/core/styles.css';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
@@ -12,8 +12,8 @@ import { useEffect, useState } from 'react';
|
|||||||
import { Airport } from '@lib/airport.types.ts';
|
import { Airport } from '@lib/airport.types.ts';
|
||||||
import AirportDrawer from '@components/AirportDrawer.tsx';
|
import AirportDrawer from '@components/AirportDrawer.tsx';
|
||||||
import { getWeatherMapUrl } from '@lib/rainViewer.ts';
|
import { getWeatherMapUrl } from '@lib/rainViewer.ts';
|
||||||
import { Switch } from '@mantine/core';
|
import { IconRadar } from '@tabler/icons-react';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
// Fix Leaflet's default icon path issues with Webpack
|
// Fix Leaflet's default icon path issues with Webpack
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
@@ -26,13 +26,19 @@ L.Icon.Default.mergeOptions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const openStreetMapUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
const openStreetMapUrl = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||||
|
const lightLayerUrl = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png';
|
||||||
|
const darkLayerUrl = 'https://{s}.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png';
|
||||||
|
// const dark1Url = 'https://maps.rainviewer.com/data/v3/5/10/11.pbf';
|
||||||
|
// const dark2Url = 'https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer/tile/2/0/3.pbf';
|
||||||
const defaultZoom = 6;
|
const defaultZoom = 6;
|
||||||
const defaultCenter: L.LatLngExpression = [38.944444, -77.455833];
|
const defaultCenter: L.LatLngExpression = [38.944444, -77.455833];
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [airport, setAirport] = useState<Airport | null>(null);
|
const [airport, setAirport] = useState<Airport | null>(null);
|
||||||
const [rainViewerUrl, setRainViewerUrl] = useState<string | null>(null);
|
const [rainViewerUrl, setRainViewerUrl] = useState<string | null>(null);
|
||||||
const [showRadar, setShowRadar] = useState<boolean>(false);
|
const initialRadarValue = Cookies.get('showRadar') === 'true';
|
||||||
|
const [showRadar, setShowRadar] = useState<boolean>(initialRadarValue);
|
||||||
|
const [baseLayer, setBaseLayer] = useState<string>(Cookies.get('selectedBaseLayer') || 'Open Street Map');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showRadar) {
|
if (showRadar) {
|
||||||
@@ -40,7 +46,25 @@ function App() {
|
|||||||
setRainViewerUrl(url);
|
setRainViewerUrl(url);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [showRadar])
|
}, [showRadar]);
|
||||||
|
|
||||||
|
function toggleRadar() {
|
||||||
|
setShowRadar(prev => {
|
||||||
|
const newValue = !prev;
|
||||||
|
Cookies.set('showRadar', newValue.toString(), { expires: 7 });
|
||||||
|
return newValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function BaseLayerChangeHandler() {
|
||||||
|
useMapEvents({
|
||||||
|
baselayerchange: (e) => {
|
||||||
|
setBaseLayer(e.name);
|
||||||
|
Cookies.set('selectedBaseLayer', e.name, { expires: 7 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='App'>
|
<div className='App'>
|
||||||
@@ -61,23 +85,27 @@ function App() {
|
|||||||
scrollWheelZoom={true}
|
scrollWheelZoom={true}
|
||||||
zoomControl={false}
|
zoomControl={false}
|
||||||
>
|
>
|
||||||
|
<LayersControl>
|
||||||
|
<LayersControl.BaseLayer checked={baseLayer === 'Open Street Map'} name={'Open Street Map'}>
|
||||||
|
<TileLayer url={openStreetMapUrl} />
|
||||||
|
</LayersControl.BaseLayer>
|
||||||
|
<LayersControl.BaseLayer checked={baseLayer === 'Carto Light'} name={'Carto Light'}>
|
||||||
|
<TileLayer url={lightLayerUrl} />
|
||||||
|
</LayersControl.BaseLayer>
|
||||||
|
<LayersControl.BaseLayer checked={baseLayer === 'Carto Dark'} name={'Carto Dark'}>
|
||||||
|
<TileLayer url={darkLayerUrl} />
|
||||||
|
</LayersControl.BaseLayer>
|
||||||
|
</LayersControl>
|
||||||
|
{rainViewerUrl && showRadar && <TileLayer url={rainViewerUrl} opacity={0.5} zIndex={5} />}
|
||||||
<ZoomControl position={'bottomright'} />
|
<ZoomControl position={'bottomright'} />
|
||||||
<TileLayer url={openStreetMapUrl} />
|
|
||||||
{ rainViewerUrl && showRadar && (
|
|
||||||
<TileLayer url={rainViewerUrl} opacity={0.5} zIndex={5} />
|
|
||||||
)}
|
|
||||||
<AirportLayer setAirport={setAirport} />
|
<AirportLayer setAirport={setAirport} />
|
||||||
|
<BaseLayerChangeHandler />
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
<div style={{
|
<IconRadar
|
||||||
position: 'absolute', top: '100px', right: '10px', zIndex: 1000, backgroundColor: '#fff',
|
onClick={toggleRadar}
|
||||||
borderRadius: '8px', boxShadow: '0 2px 6px rgba(0,0,0,0.15)', padding: '10px'
|
style={{ bottom: '80px' }}
|
||||||
}}>
|
className={`map-button ${showRadar ? 'active' : ''}`}
|
||||||
<Switch
|
/>
|
||||||
label="Radar"
|
|
||||||
checked={showRadar}
|
|
||||||
onChange={(event) => setShowRadar(event.currentTarget.checked)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -84,9 +84,9 @@ export default function AirportLayer({ setAirport }: { setAirport: (airport: Air
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{sortedAirports.map((airport, index) => {
|
{sortedAirports.map((airport, index) => (
|
||||||
return <AirportMarker airport={airport} index={index} setAirport={setAirport} />;
|
<AirportMarker key={index} airport={airport} index={index} setAirport={setAirport} />
|
||||||
})}
|
))}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,9 +126,6 @@ export function Header() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(Cookies.get('logged_in'));
|
|
||||||
console.log(Cookies.get('session'));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box>
|
<Box>
|
||||||
|
|||||||
@@ -13,22 +13,19 @@ async function getWeatherMaps(): Promise<WeatherMaps | undefined> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const rainViewerUrl = 'https://tilecache.rainviewer.com/v2/radar/1744386000/256/{z}/{x}/{y}/2/1_1.png';
|
|
||||||
// const rainViewerUrl = 'https://tilecache.rainviewer.com/v2/radar/1744386000/256/10/290/391/2/1_1.png'
|
|
||||||
// https://api.rainviewer.com/public/weather-maps.json
|
|
||||||
export async function getWeatherMapUrl(): Promise<string | null> {
|
export async function getWeatherMapUrl(): Promise<string | null> {
|
||||||
const weatherMaps = await getWeatherMaps();
|
const weatherMaps = await getWeatherMaps();
|
||||||
if (weatherMaps != undefined) {
|
if (weatherMaps != undefined) {
|
||||||
let url = weatherMaps.host;
|
let url = weatherMaps.host;
|
||||||
|
// url = 'https://cdn.rainviewer.com';
|
||||||
let latest = "";
|
let latest = "";
|
||||||
if (weatherMaps.radar.nowcast.length > 0) {
|
if (weatherMaps.radar.past.length > 0) {
|
||||||
latest = weatherMaps.radar.nowcast[weatherMaps.radar.nowcast.length - 1].path;
|
|
||||||
} else 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";
|
||||||
return url;
|
return url;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user