mirror of
https://github.com/Sosokker/openweathermap-dashboard.git
synced 2025-12-18 13:44:04 +01:00
feat: add nginx to serve report
This commit is contained in:
parent
525cccf344
commit
69f57767bb
15
cmd/Dockerfile
Normal file
15
cmd/Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM golang:1.23.5-alpine
|
||||
|
||||
WORKDIR /usr/share/openweather-dashboard
|
||||
|
||||
COPY go.mod .
|
||||
|
||||
COPY go.sum .
|
||||
|
||||
RUN go mod download
|
||||
|
||||
COPY ../. .
|
||||
|
||||
RUN go build -o main .
|
||||
|
||||
CMD ["./main"]
|
||||
24
docker-compose.yml
Normal file
24
docker-compose.yml
Normal file
@ -0,0 +1,24 @@
|
||||
services:
|
||||
backend:
|
||||
container_name: backend
|
||||
build: ./cmd
|
||||
networks:
|
||||
- internal-net
|
||||
environment:
|
||||
- OPENWEATHERMAP_API_KEY=34fec0bf50b5e8cc6c2070005ea3d5b0
|
||||
|
||||
nginx:
|
||||
build:
|
||||
context: nginx
|
||||
container_name: nginx
|
||||
volumes:
|
||||
- ./nginx:/etc/nginx/conf.d/
|
||||
networks:
|
||||
- internal-net
|
||||
ports:
|
||||
- "8080:80"
|
||||
depends_on:
|
||||
- backend
|
||||
|
||||
networks:
|
||||
internal-net:
|
||||
14
nginx/Dockerfile
Normal file
14
nginx/Dockerfile
Normal file
@ -0,0 +1,14 @@
|
||||
FROM nginx:alpine
|
||||
|
||||
WORKDIR /etc/nginx
|
||||
|
||||
COPY ./nginx.conf ./conf.d/default.conf
|
||||
|
||||
COPY ./web/index.html ./html/report/index.html
|
||||
COPY ./web/index.css ./html/report/index.css
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT [ "nginx" ]
|
||||
|
||||
CMD [ "-g", "daemon off;" ]
|
||||
34
nginx/nginx.conf
Normal file
34
nginx/nginx.conf
Normal file
@ -0,0 +1,34 @@
|
||||
upstream api {
|
||||
server backend:8080;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header 'Access-Control-Max-Age' 1728000;
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,
|
||||
X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
|
||||
add_header 'Content-Type' 'application/json';
|
||||
add_header 'Content-Length' 0;
|
||||
return 204;
|
||||
}
|
||||
|
||||
add_header 'Access-Control-Allow-Origin' '*';
|
||||
add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,
|
||||
X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
|
||||
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH';
|
||||
|
||||
proxy_pass http://backend:8080;
|
||||
}
|
||||
|
||||
location /report {
|
||||
root html;
|
||||
index index.html;
|
||||
}
|
||||
}
|
||||
57
nginx/web/index.css
Normal file
57
nginx/web/index.css
Normal file
@ -0,0 +1,57 @@
|
||||
#main-section {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 2.5rem
|
||||
}
|
||||
|
||||
#statistic {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#table-section {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#map {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
flex: 2;
|
||||
min-width: 600px;
|
||||
}
|
||||
|
||||
|
||||
// from https://codepen.io/zass-udd/pen/NWqKmdE
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: center;
|
||||
padding:6px 20px;
|
||||
}
|
||||
tr th {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: center;
|
||||
padding:6px 20px;
|
||||
background-color: #ad1e23;
|
||||
color:#fff;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #dddddd;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content td, .content th {
|
||||
border-top: 1px solid transparent;
|
||||
padding: 2px 10px 2px 15px;
|
||||
}
|
||||
213
nginx/web/index.html
Normal file
213
nginx/web/index.html
Normal file
@ -0,0 +1,213 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Graph</title>
|
||||
<script src="https://cdn.plot.ly/plotly-3.0.0.min.js" charset="utf-8"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" href="./index.css">
|
||||
|
||||
<style>
|
||||
body {
|
||||
font-family: "Roboto", sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="main-section">
|
||||
<div id="statistic">
|
||||
<h2>Rainfall Statistics</h2>
|
||||
<p>Today rain/hr.</p>
|
||||
<div id="table-section">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th rowspan="2"><strong>Location</strong></th>
|
||||
<th colspan="2"><strong>Coordinate</strong></th>
|
||||
<th rowspan="2"><strong>Rain Per Hour</strong></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Latitude</th>
|
||||
<th>Longitude</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>0.395</td>
|
||||
<td>±8</td>
|
||||
<td>50.27</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>0.617</td>
|
||||
<td>±6</td>
|
||||
<td>78.54</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>0.888</td>
|
||||
<td>±6</td>
|
||||
<td>113.10</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>16</td>
|
||||
<td>1.580</td>
|
||||
<td>±5</td>
|
||||
<td>201.06</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>20</td>
|
||||
<td>2.470</td>
|
||||
<td>±5</td>
|
||||
<td>314.16</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>22</td>
|
||||
<td>2.984</td>
|
||||
<td>±4</td>
|
||||
<td>380.13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>25</td>
|
||||
<td>3.850</td>
|
||||
<td>±4</td>
|
||||
<td>490.88</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>28</td>
|
||||
<td>4.840</td>
|
||||
<td>±4</td>
|
||||
<td>615.75</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>32</td>
|
||||
<td>6.310</td>
|
||||
<td>±4</td>
|
||||
<td>804.25</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="map">
|
||||
<script>
|
||||
async function fetchData(scale) {
|
||||
let url = "http://localhost:8080/api/data";
|
||||
if (scale === true) {
|
||||
url += "?scale=true"
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
const weatherData = await res.json()
|
||||
return weatherData
|
||||
} catch {
|
||||
console.error(error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<div id="rain-marker">
|
||||
<script>
|
||||
async function updateRainMarker() {
|
||||
const weatherData = await fetchData(false);
|
||||
if (!weatherData) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lon = weatherData.map((location) => location.coord.lon);
|
||||
const lat = weatherData.map((location) => location.coord.lat);
|
||||
const text = weatherData.map(
|
||||
(location) =>
|
||||
`${location.name}: ${location.rain["1h"]} mm/hr (${location.weather[0].description})`
|
||||
);
|
||||
|
||||
const data = [
|
||||
{
|
||||
type: "scattergeo",
|
||||
mode: "markers+text",
|
||||
lon: lon,
|
||||
lat: lat,
|
||||
marker: {
|
||||
color: "rgb(17, 157, 255)",
|
||||
size: 10,
|
||||
},
|
||||
text: text,
|
||||
textposition: "bottom right",
|
||||
},
|
||||
];
|
||||
|
||||
const layout = {
|
||||
map: { center: { lon: -110, lat: 50 }, zoom: 3.3 },
|
||||
geo: {
|
||||
center: { lon: -100, lat: 40 },
|
||||
zoom: 3,
|
||||
},
|
||||
showlegend: false,
|
||||
height: 500,
|
||||
width: 750,
|
||||
};
|
||||
|
||||
Plotly.newPlot("rain-marker", data, layout);
|
||||
}
|
||||
|
||||
updateRainMarker();
|
||||
</script>
|
||||
</div>
|
||||
<div id="rain-density">
|
||||
<script>
|
||||
async function updateRainDensity() {
|
||||
const weatherData = await fetchData(true);
|
||||
if (!weatherData) {
|
||||
console.error("Failed to fetch weather data for rain-density.");
|
||||
return;
|
||||
}
|
||||
|
||||
const locations = weatherData.map((location) => location.coord.place);
|
||||
console.log(weatherData)
|
||||
console.log(locations)
|
||||
const z = weatherData.map((location) => location.rain["1h"]);
|
||||
var data = [
|
||||
{
|
||||
type: "choroplethmap",
|
||||
name: "Rainfall Per Hour",
|
||||
geojson:
|
||||
"https://raw.githubusercontent.com/python-visualization/folium/master/examples/data/us-states.json",
|
||||
// state-id, rainfall (scale 100)
|
||||
locations: locations,
|
||||
z: z,
|
||||
zmin: 0,
|
||||
zmax: 100,
|
||||
colorscale: "Blues",
|
||||
reversescale: true,
|
||||
colorbar: {
|
||||
y: 0,
|
||||
yanchor: "bottom",
|
||||
title: { text: "Rainfall Per Hour", side: "right" },
|
||||
marker: { line: { width: 1, color: "white" } },
|
||||
},
|
||||
},
|
||||
];
|
||||
var layout = {
|
||||
map: { style: "dark", center: { lon: -110, lat: 50 }, zoom: 3.3 },
|
||||
width: 750,
|
||||
height: 500,
|
||||
margin: { t: 0, b: 0 },
|
||||
};
|
||||
Plotly.newPlot("rain-density", data, layout);
|
||||
}
|
||||
|
||||
updateRainDensity();
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Loading…
Reference in New Issue
Block a user