From ff45876377e2b410a24b3416db3f8fb8902afc57 Mon Sep 17 00:00:00 2001 From: Guntis Smaukstelis Date: Mon, 15 May 2023 20:50:53 +0300 Subject: [PATCH] Implement aggregation frontend, also http on prod --- fly.toml | 2 +- web/.env.production | 2 +- web/package-lock.json | 19 +++++++-- web/package.json | 1 + web/src/App.tsx | 30 ++++++++------ web/src/aggregator/Aggregator.tsx | 53 ++++++++++++++++++++++++ web/src/aggregator/Result.tsx | 55 +++++++++++++++++++++++++ web/src/aggregator/SelectAggregator.tsx | 34 +++++++++++++++ web/src/aggregator/SelectCity.tsx | 34 +++++++++++++++ web/src/aggregator/SelectTimeRange.tsx | 32 ++++++++++++++ web/src/consts.ts | 6 +++ web/src/css/App.module.css | 9 ++++ 12 files changed, 259 insertions(+), 18 deletions(-) create mode 100644 web/src/aggregator/Aggregator.tsx create mode 100644 web/src/aggregator/Result.tsx create mode 100644 web/src/aggregator/SelectAggregator.tsx create mode 100644 web/src/aggregator/SelectCity.tsx create mode 100644 web/src/aggregator/SelectTimeRange.tsx diff --git a/fly.toml b/fly.toml index eb80183..74e3344 100644 --- a/fly.toml +++ b/fly.toml @@ -8,7 +8,7 @@ primary_region = "waw" [http_service] internal_port = 8080 - force_https = true + force_https = false auto_stop_machines = false auto_start_machines = true diff --git a/web/.env.production b/web/.env.production index 602ea08..bf69970 100644 --- a/web/.env.production +++ b/web/.env.production @@ -1 +1 @@ -VITE_API_HOST=https://weather-tool.fly.dev \ No newline at end of file +VITE_API_HOST=http://weather-tool.fly.dev \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index ccb841d..5a4a4da 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,14 +1,14 @@ { - "name": "vite-template-solid", + "name": "weather-tool-web", "version": "0.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "vite-template-solid", + "name": "weather-tool-web", "version": "0.0.0", - "license": "MIT", "dependencies": { + "moment": "^2.29.4", "solid-js": "^1.6.10" }, "devDependencies": { @@ -1279,6 +1279,14 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2390,6 +2398,11 @@ "is-what": "^4.1.8" } }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/web/package.json b/web/package.json index 38ef122..86933af 100644 --- a/web/package.json +++ b/web/package.json @@ -14,6 +14,7 @@ "vite-plugin-solid": "^2.5.0" }, "dependencies": { + "moment": "^2.29.4", "solid-js": "^1.6.10" } } diff --git a/web/src/App.tsx b/web/src/App.tsx index 00cd43c..10b293d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,24 +1,28 @@ -import type { Component } from 'solid-js'; +import { Component, createSignal } from 'solid-js'; +import { Aggregator } from './aggregator/Aggregator'; -import logo from './assets/logo.svg'; import styles from './css/App.module.css'; import { FileManager } from './fileManager/FileManager'; console.log("env:", import.meta.env.MODE); console.log("api host:", import.meta.env.VITE_API_HOST); +type Section = "aggregator" | "fileManager"; + const App: Component = () => { - return ( -
- -
- logo -

- Weather tool web -

-
-
- ); + const [getSection, setSection] = createSignal
("aggregator"); + + const section = () => getSection() === "aggregator" ? : ; + + return ( +
+
+ setSection("aggregator")}>aggregator | + setSection("fileManager")}>file manager +
+ { section() } +
+ ); }; export default App; diff --git a/web/src/aggregator/Aggregator.tsx b/web/src/aggregator/Aggregator.tsx new file mode 100644 index 0000000..a75f8e4 --- /dev/null +++ b/web/src/aggregator/Aggregator.tsx @@ -0,0 +1,53 @@ +import { createSignal } from "solid-js"; +import { Result } from "./Result"; +import { SelectAggregator } from "./SelectAggregator"; +import { SelectCity } from "./SelectCity"; +import { SelectTimeRange } from "./SelectTimeRange"; + +const dayAgoMs = Date.now() - 24 * 60 * 60 * 1000; + +export function Aggregator() { + const [getCities, setCities] = createSignal>(new Set([])); + const [getStart, setStart] = createSignal(new Date(dayAgoMs)); + const [getEnd, setEnd] = createSignal(new Date()); + const [getField, setField] = createSignal("tempMax"); + const [getKey, setKey] = createSignal("max"); + + return ( +
+

Aggregator

+
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/web/src/aggregator/Result.tsx b/web/src/aggregator/Result.tsx new file mode 100644 index 0000000..a00ab74 --- /dev/null +++ b/web/src/aggregator/Result.tsx @@ -0,0 +1,55 @@ +import moment from "moment"; +import { Accessor, Component, createResource, createSignal } from "solid-js"; +import { apiHost } from "../consts"; + +export const Result: Component<{ + getCities: Accessor>, + getStart: Accessor, + getEnd: Accessor, + getField: Accessor, + getKey: Accessor, +}> = (props) => { + const cities = () => [...props.getCities()].join(",") + const startStr = () => moment(props.getStart()).format("YYYY/MM/DD, HH:mm"); + const endStr = () => moment(props.getEnd()).format("YYYY/MM/DD, HH:mm"); + const queryStart = () => moment(props.getStart()).format("YYYYMMDD_HHmm"); + const queryEnd = () => moment(props.getEnd()).format("YYYYMMDD_HHmm"); + const [getTimestamp, setTimestamp] = createSignal(0); + + const fetchQuery = async (timestamp: number) => { + console.log("query:", new Date(timestamp)); + if (cities() === "") return "ERROR: Select cities!"; + await new Promise(resolve => setTimeout(resolve, 500)) + const response = await fetch(`${apiHost}/api/query/${queryStart()}-${queryEnd()}/${cities()}/${props.getField()}/${props.getKey()}`); + const json = await response.json(); + return json; + } + + const [queryResource] = createResource(getTimestamp, fetchQuery); + + return ( +
+

Aggregate by:

+ setTimestamp(Date.now())} /> +

cities: { cities() }

+

time range: { startStr() } - { endStr() }

+

weather field: { props.getField() }

+

aggregation field: { props.getKey() }

+

Result:

+

+ { queryResource.loading && ( +

+ + Loading query +
+ )} + { queryResource.error && ( +
Error while querying: ${queryResource.error}
+ )} + { queryResource() && ( + {JSON.stringify(queryResource())} + )} +

+
+ ); +} \ No newline at end of file diff --git a/web/src/aggregator/SelectAggregator.tsx b/web/src/aggregator/SelectAggregator.tsx new file mode 100644 index 0000000..9cdaea2 --- /dev/null +++ b/web/src/aggregator/SelectAggregator.tsx @@ -0,0 +1,34 @@ +import { Accessor, Component, Setter } from "solid-js"; +import { aggregateKey, weatherField } from "../consts"; + +export const SelectAggregator: Component<{ + getField: Accessor, + setField: Setter, + getKey: Accessor, + setKey: Setter, +}> = (props) => { + return ( +
+

Select aggregator

+ +
    + { aggregateKey.map(key => + + )} +
+
+ ); +} \ No newline at end of file diff --git a/web/src/aggregator/SelectCity.tsx b/web/src/aggregator/SelectCity.tsx new file mode 100644 index 0000000..a5ad5fe --- /dev/null +++ b/web/src/aggregator/SelectCity.tsx @@ -0,0 +1,34 @@ +import { Accessor, Component, Setter } from "solid-js"; +import { cityList } from "../consts"; + +export const SelectCity: Component<{ + getCities: Accessor>, + setCities: Setter>, +}> = (props) => { + function handleSelect(e: MouseEvent) { + if (!e.target) return; + const target = e.target as HTMLInputElement; + const { checked: selected, value: city } = target; + const cityList = new Set([...props.getCities()]); + if (selected) cityList.add(city); + else cityList.delete(city); + + props.setCities(cityList); + } + + return ( +
    {cityList.map(city => + + )}
+ ); +}; \ No newline at end of file diff --git a/web/src/aggregator/SelectTimeRange.tsx b/web/src/aggregator/SelectTimeRange.tsx new file mode 100644 index 0000000..7f0088f --- /dev/null +++ b/web/src/aggregator/SelectTimeRange.tsx @@ -0,0 +1,32 @@ +import moment from 'moment'; +import { Accessor, Component, Setter } from "solid-js"; + +export const SelectTimeRange: Component<{ + getStart: Accessor, + setStart: Setter, + getEnd: Accessor, + setEnd: Setter, +}> = (props) => { + const startStr = () => moment(props.getStart()).format("YYYY-MM-DDTHH:mm"); + const endStr = () => moment(props.getEnd()).format("YYYY-MM-DDTHH:mm"); + + return ( +
+

Select time range

+

+ props.setStart(new Date(e.target.value))} + /> start +

+

+ props.setEnd(new Date(e.target.value))} + /> end +

+
+ ); +} \ No newline at end of file diff --git a/web/src/consts.ts b/web/src/consts.ts index 239a460..3564d3b 100644 --- a/web/src/consts.ts +++ b/web/src/consts.ts @@ -1 +1,7 @@ export const apiHost = import.meta.env.VITE_API_HOST; + +export const cityList = ["Ainaži", "Alūksne", "Bauska", "Dagda", "Daugavgrīva", "Daugavpils", "Dobele", "Gulbene", "Jelgava", "Kalnciems", "Kolka", "Kuldīga", "Lielpēči", "Liepāja", "Madona", "Mērsrags", "Pāvilosta", "Piedruja", "Priekuļi", "Rēzekne", "Rīga", "Rucava", "Rūjiena", "Saldus", "Sigulda", "Sīļi", "Skrīveri", "Skulte", "Stende", "Ventspils", "Vičaki", "Zīlāni", "Zosēni", "Ainaži", "Alūksne", "Bauska", "Dagda", "Daugavgrīva", "Daugavpils", "Dobele", "Gulbene", "Jelgava", "Kalnciems", "Kolka", "Kuldīga", "Lielpēči", "Liepāja", "Madona", "Mērsrags", "Pāvilosta", "Piedruja", "Priekuļi", "Rēzekne", "Rīga", "Rucava", "Rūjiena", "Saldus", "Sigulda", "Sīļi", "Skrīveri", "Skulte", "Stende", "Ventspils", "Vičaki", "Zīlāni", "Zosēni"]; + +export const weatherField = ["tempMax", "tempMin", "tempAvg", "precipitation", "windAvg", "windMax", "visibilityMin", "visibilityAvg", "snowAvg", "atmPressire", "dewPoint", "humidity", "sunDuration", "phenomena"]; + +export const aggregateKey = ["min", "max", "avg", "sum", "list", "distinct"]; diff --git a/web/src/css/App.module.css b/web/src/css/App.module.css index 48308b2..d156436 100644 --- a/web/src/css/App.module.css +++ b/web/src/css/App.module.css @@ -23,6 +23,15 @@ color: #b318f0; } +.sections { + position: absolute; + top: 0px; + left: 0px; + padding-left: 20px; + line-height: 70px; + text-align: left; +} + @keyframes logo-spin { from { transform: rotate(0deg);