From bd41cbc40d8d025218902a9f2701abf6d559bebe Mon Sep 17 00:00:00 2001
From: store <store>
Date: Fri, 10 Nov 2023 10:04:46 +0100
Subject: [PATCH 01/16] bug: remove a spo join from station

---
 api/core/eea/dataflows.py       | 3 ---
 api/endpoints/version/routes.py | 2 +-
 2 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/api/core/eea/dataflows.py b/api/core/eea/dataflows.py
index d7647c8..c2c768a 100644
--- a/api/core/eea/dataflows.py
+++ b/api/core/eea/dataflows.py
@@ -253,9 +253,6 @@ class Dataflows:
                       ST_Z(st.geom) altitude,
                       ST_SRID(st.geom) epsg
                   FROM stations st
-                      LEFT OUTER JOIN sampling_points sp ON st.id = sp.station_id
-                  WHERE 
-                      sp.private = false
                   GROUP BY
                       st.id,  
                       st.name,
diff --git a/api/endpoints/version/routes.py b/api/endpoints/version/routes.py
index e054571..c8ad9d9 100644
--- a/api/endpoints/version/routes.py
+++ b/api/endpoints/version/routes.py
@@ -3,7 +3,7 @@ from flask_jwt_extended import create_access_token
 import requests
 
 version_endpoint = Blueprint('version', __name__)
-current_version = "3.0.16"
+current_version = "3.0.17"
 
 
 @version_endpoint.route('/api/version', methods=['GET'])
-- 
GitLab


From 5cdb89c3395fc5de0a5ccab956fad12ac2cccdb8 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 18 Jan 2024 11:36:31 +0100
Subject: [PATCH 02/16] docker and config changes

---
 .vscode/settings.json | 2 +-
 Dockerfile.api        | 1 +
 api/config.py         | 2 +-
 docker-compose.yml    | 4 ++--
 4 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/.vscode/settings.json b/.vscode/settings.json
index 85c57c0..30b4731 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,6 +1,6 @@
 {
   "html.format.wrapLineLength": 0,
-  "python.formatting.autopep8Args": ["--max-line-length=20000"],
+  "autopep8.args": ["--max-line-length=20000"],
   "editor.defaultFormatter": "esbenp.prettier-vscode",
   "editor.tabSize": 2,
   "editor.insertSpaces": true,
diff --git a/Dockerfile.api b/Dockerfile.api
index f6e3bac..de7784f 100644
--- a/Dockerfile.api
+++ b/Dockerfile.api
@@ -4,6 +4,7 @@ RUN apt -y install
 WORKDIR /app
 COPY api/requirements.txt .
 RUN pip install -r requirements.txt
+COPY .env ../
 COPY api/ ./
 ENV PYTHONUNBUFFERED=1
 EXPOSE 5000
diff --git a/api/config.py b/api/config.py
index e8b46c6..01be3e4 100644
--- a/api/config.py
+++ b/api/config.py
@@ -4,7 +4,7 @@ from datetime import datetime
 from datetime import timedelta
 
 basedir = os.path.abspath(os.path.dirname(__file__))
-load_dotenv(os.path.join(basedir, '.env'))
+load_dotenv(os.path.join(basedir, '../.env'))
 
 
 class Config(object):
diff --git a/docker-compose.yml b/docker-compose.yml
index f18ea6d..5c44242 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -8,7 +8,7 @@ services:
     sysctls:
       - net.ipv4.tcp_keepalive_time=200
     ports:
-      - 5000:5000
+      - ${API_PORT:-5000}:5000
   client:
     build:
       context: .
@@ -18,4 +18,4 @@ services:
     depends_on:
       - api
     ports:
-      - "80:80"
+      - ${CLIENT_PORT:-80}:80
-- 
GitLab


From bbe64758b7945185b4995541e43882b5fce98d14 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Fri, 19 Jan 2024 14:52:28 +0100
Subject: [PATCH 03/16] added pre aggregation functionality

---
 README.md                                     |   20 +-
 api/core/endpoints.py                         |    3 +
 .../management/preaggregation/models.py       |    5 +
 .../management/preaggregation/routes.py       |   39 +
 client/src/components/MenuBar.vue             |    3 +-
 client/src/router.js                          |    2 +
 .../preaggregation/PreAggregation.vue         |   54 +
 .../management/preaggregation/service.js      |    8 +
 client/yarn.lock                              | 1250 -----------------
 cron/refresh_views.py                         |   25 +
 sql/pre_aggregates.sql                        |  325 +++++
 11 files changed, 482 insertions(+), 1252 deletions(-)
 create mode 100644 api/endpoints/management/preaggregation/models.py
 create mode 100644 api/endpoints/management/preaggregation/routes.py
 create mode 100644 client/src/views/management/preaggregation/PreAggregation.vue
 create mode 100644 client/src/views/management/preaggregation/service.js
 delete mode 100644 client/yarn.lock
 create mode 100644 cron/refresh_views.py
 create mode 100644 sql/pre_aggregates.sql

diff --git a/README.md b/README.md
index b5b78d5..0c64b4f 100644
--- a/README.md
+++ b/README.md
@@ -28,9 +28,11 @@ git clone https://git.nilu.no/raven/raven-administration
 
 ## Set environment varables
 
-**Create a file called `.env` in the `api` folder and set the variables**
+**Create a file called `.env` in the `root` folder and set the variables**
 
 ```
+API_PORT=5000
+CLIENT_PORT=80
 DB_URI = postgresql://postgres:password@host:5432/database
 JWT_ACCESS_TOKEN_EXPIRES_SECONDS = 3600
 JWT_SECRET_KEY = make-up-a-secure-key
@@ -90,3 +92,19 @@ flask run
 # from inside the client folder start the frontend
 npm run dev
 ```
+
+## Pre aggregations
+
+Aggregating data can be triggered manually in the raven app.  
+However, it is recommended to set up a schedule to trigger the aggregation on a daily basis.  
+The sql command that needs to run is `select raven_refresh_aggregates()`  
+One ways is to use the postgres extension `pgagent`. This will enable a schedular within postgres.  
+Another way is to set up a `cron` job in linux or a `schtasks` in windows
+
+The `cron` folder has a python script that can be used together with a schedular.  
+It does require `psycopg2-binary==2.9.5`, so make sure this is available for the scheduled task.
+
+```powershell
+# Example of how to set up a scheduled task in Windows
+schtasks /create /SC DAILY /TN raven-refresh-views /TR "<path_to_python> <path_to_raven>\cron\refresh_views.py" /ST 00:10
+```
diff --git a/api/core/endpoints.py b/api/core/endpoints.py
index aa4da2b..0c05a88 100644
--- a/api/core/endpoints.py
+++ b/api/core/endpoints.py
@@ -66,6 +66,9 @@ class Endpoints:
         from endpoints.data.dataflow.routes import dataflow_endpoint
         app.register_blueprint(dataflow_endpoint)
 
+        from endpoints.management.preaggregation.routes import preagg_endpoint
+        app.register_blueprint(preagg_endpoint)
+
         from endpoints.data.map.routes import map_endpoint
         app.register_blueprint(map_endpoint)
 
diff --git a/api/endpoints/management/preaggregation/models.py b/api/endpoints/management/preaggregation/models.py
new file mode 100644
index 0000000..94f649f
--- /dev/null
+++ b/api/endpoints/management/preaggregation/models.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel
+
+
+class AggModel(BaseModel):
+    type: str
diff --git a/api/endpoints/management/preaggregation/routes.py b/api/endpoints/management/preaggregation/routes.py
new file mode 100644
index 0000000..95df2ee
--- /dev/null
+++ b/api/endpoints/management/preaggregation/routes.py
@@ -0,0 +1,39 @@
+from flask import jsonify, Blueprint, request
+from endpoints.management.preaggregation.models import AggModel
+from core.database import CursorFromPool
+from core.jwt_ext_custom import jwt_required_with_management_claim
+from core.query import Q
+preagg_endpoint = Blueprint('preagg', __name__)
+
+
+@preagg_endpoint.route('/api/management/preaggregation', methods=['GET'])
+@jwt_required_with_management_claim()
+def preagg():
+    with CursorFromPool() as cursor:
+        sql = f"""
+            select 'year' as type, round(avg(cov)) as avg_cov, count(*) as count_val, count(distinct sampling_point_id) count_sp, to_char(min(time),'yyyy-mm-dd HH24:mi') as first_time, to_char(max(time),'yyyy-mm-dd HH24:mi') as last_time, to_char(max(created),'yyyy-mm-dd HH24:mi') as created from observations_year
+            union
+            select 'day' as type, round(avg(cov)) as avg_cov, count(*) as count_val, count(distinct sampling_point_id) count_sp, to_char(min(time),'yyyy-mm-dd HH24:mi') as first_time, to_char(max(time),'yyyy-mm-dd HH24:mi') as last_time, to_char(max(created),'yyyy-mm-dd HH24:mi') as created from observations_day
+            union
+            select 'winter season' as type, round(avg(cov)) as avg_cov, count(*) as count_val, count(distinct sampling_point_id) count_sp, to_char(min(time),'yyyy-mm-dd HH24:mi') as first_time, to_char(max(time),'yyyy-mm-dd HH24:mi') as last_time,to_char(max(created),'yyyy-mm-dd HH24:mi') as created from observations_winter_season
+            union
+            select 'winter year' as type, round(avg(cov)) as avg_cov, count(*) as count_val, count(distinct sampling_point_id) count_sp, to_char(min(time),'yyyy-mm-dd HH24:mi') as first_time, to_char(max(time),'yyyy-mm-dd HH24:mi') as last_time, to_char(max(created),'yyyy-mm-dd HH24:mi') as created from observations_winter_year
+            union
+            select 'summer year' as type, round(avg(cov)) as avg_cov, count(*) as count_val, count(distinct sampling_point_id) count_sp, to_char(min(time),'yyyy-mm-dd HH24:mi') as first_time, to_char(max(time),'yyyy-mm-dd HH24:mi') as last_time, to_char(max(created),'yyyy-mm-dd HH24:mi') as created from observations_summer_year
+            union
+            select 'aot40 vegetation' as type, round(avg(cov)) as avg_cov, count(*) as count_val, count(distinct sampling_point_id) count_sp, to_char(min(time),'yyyy-mm-dd HH24:mi') as first_time, to_char(max(time),'yyyy-mm-dd HH24:mi') as last_time, to_char(max(created),'yyyy-mm-dd HH24:mi') as created from observations_aot40v
+            union
+            select 'aot40 forrest' as type, round(avg(cov)) as avg_cov, count(*) as count_val, count(distinct sampling_point_id) count_sp, to_char(min(time),'yyyy-mm-dd HH24:mi') as first_time, to_char(max(time),'yyyy-mm-dd HH24:mi') as last_time, to_char(max(created),'yyyy-mm-dd HH24:mi') as created from observations_aot40f
+            order by type
+        """
+        cursor.execute(sql)
+        values = cursor.fetchall()
+        return jsonify(values)
+
+
+@preagg_endpoint.route('/api/management/preaggregation/update', methods=['GET'])
+@jwt_required_with_management_claim()
+def preagg_update():
+    with CursorFromPool() as cursor:
+        cursor.execute("select raven_refresh_aggregates();")
+        return jsonify({'message': 'success'})
diff --git a/client/src/components/MenuBar.vue b/client/src/components/MenuBar.vue
index ac30c85..3f59320 100644
--- a/client/src/components/MenuBar.vue
+++ b/client/src/components/MenuBar.vue
@@ -44,7 +44,8 @@ const getmodules = () => {
         { name: "Assessment Regimes", comp: "AssessmentRegimes", show: jwt.management && jwt.allnetworks },
         { name: "Attainments", comp: "Attainments", show: jwt.management && jwt.allnetworks },
         { name: "Exceedances", comp: "Exceedances", show: jwt.management && jwt.allnetworks },
-        { name: "Settings", comp: "Settings", show: jwt.management && jwt.allnetworks }
+        { name: "Settings", comp: "Settings", show: jwt.management && jwt.allnetworks },
+        { name: "Pre aggregation", comp: "PreAggregation", show: jwt.data }
       ]
     },
     {
diff --git a/client/src/router.js b/client/src/router.js
index 034ba25..4e7e2c1 100644
--- a/client/src/router.js
+++ b/client/src/router.js
@@ -16,6 +16,7 @@ const AssessmentRegimes = () => import("./views/management/assessmentregimes/Ass
 const Attainments = () => import("./views/management/attainments/Attainments.vue");
 const Exceedances = () => import("./views/management/exceedances/Exceedances.vue");
 const Settings = () => import("./views/management/settings/Settings.vue");
+const PreAggregation = () => import("./views/management/preaggregation/PreAggregation.vue");
 
 const Latest = () => import("./views/data/latest/Latest.vue");
 const Historical = () => import("./views/data/historical/Historical.vue");
@@ -53,6 +54,7 @@ const routes = [
   { path: "/management/attainments", component: Attainments, name: "Attainments" },
   { path: "/management/exceedances", component: Exceedances, name: "Exceedances" },
   { path: "/management/settings", component: Settings, name: "Settings" },
+  { path: "/management/preaggregation", component: PreAggregation, name: "PreAggregation" },
 
   { path: "/data/latest", component: Latest, name: "Latest" },
   { path: "/data/historical", component: Historical, name: "Historical" },
diff --git a/client/src/views/management/preaggregation/PreAggregation.vue b/client/src/views/management/preaggregation/PreAggregation.vue
new file mode 100644
index 0000000..dee582e
--- /dev/null
+++ b/client/src/views/management/preaggregation/PreAggregation.vue
@@ -0,0 +1,54 @@
+<script setup>
+import Service from "./service";
+import Eventy from "../../../helpers/eventy";
+
+const data = ref([]);
+
+onMounted(async () => {
+  await loadData();
+});
+
+const loadData = async () => {
+  data.value = await Service.get();
+};
+
+const update = async () => {
+  Eventy.showMessage("Aggregating data. Please wait", "loading");
+  await Service.update();
+  await loadData();
+  Eventy.hideMessage();
+};
+</script>
+
+<template>
+  <common-layout>
+    <tool-bar title="Pre aggregation" :show-column-picker="false" :show-add="false" :show-download="false" :show-filter="false" />
+
+    <container>
+      <div class="font-bold">Pre aggregating the data may take a while, depending on the amount of data stored in the database</div>
+      <div><button class="n-button" @click="update">Manually aggregate data</button></div>
+    </container>
+    <div class="mt-4">
+      <table id="preaggId" class="n-table">
+        <tr>
+          <th>Aggregation type</th>
+          <th>Values count</th>
+          <th>Samplingpoints count</th>
+          <th>Average coverage</th>
+          <!-- <th>First date</th> -->
+          <th>Last date</th>
+          <th>Last updated</th>
+        </tr>
+        <tr v-for="row in data">
+          <td>{{ row.type }}</td>
+          <td>{{ row.count_val }}</td>
+          <td>{{ row.count_sp }}</td>
+          <td>{{ row.avg_cov ? row.avg_cov + "%" : "-" }}</td>
+          <!-- <td>{{ row.first_time }}</td> -->
+          <td>{{ row.last_time ? row.last_time : "-" }}</td>
+          <td class="font-bold">{{ row.created ? row.created : "-" }}</td>
+        </tr>
+      </table>
+    </div>
+  </common-layout>
+</template>
diff --git a/client/src/views/management/preaggregation/service.js b/client/src/views/management/preaggregation/service.js
new file mode 100644
index 0000000..e304a8a
--- /dev/null
+++ b/client/src/views/management/preaggregation/service.js
@@ -0,0 +1,8 @@
+import { Get, Post } from "../../../helpers/request";
+
+const Service = {
+  get: async () => Get("/api/management/preaggregation"),
+  update: async () => Get("/api/management/preaggregation/update")
+};
+
+export default Service;
diff --git a/client/yarn.lock b/client/yarn.lock
deleted file mode 100644
index 169e87f..0000000
--- a/client/yarn.lock
+++ /dev/null
@@ -1,1250 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@antfu/install-pkg@^0.1.1":
-  "integrity" "sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ=="
-  "resolved" "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-0.1.1.tgz"
-  "version" "0.1.1"
-  dependencies:
-    "execa" "^5.1.1"
-    "find-up" "^5.0.0"
-
-"@antfu/utils@^0.5.2":
-  "integrity" "sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA=="
-  "resolved" "https://registry.npmjs.org/@antfu/utils/-/utils-0.5.2.tgz"
-  "version" "0.5.2"
-
-"@antfu/utils@^0.7.2":
-  "integrity" "sha512-vy9fM3pIxZmX07dL+VX1aZe7ynZ+YyB0jY+jE6r3hOK6GNY2t6W8rzpFC4tgpbXUYABkFQwgJq2XYXlxbXAI0g=="
-  "resolved" "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.2.tgz"
-  "version" "0.7.2"
-
-"@babel/parser@^7.15.8", "@babel/parser@^7.16.4":
-  "integrity" "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA=="
-  "resolved" "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz"
-  "version" "7.20.5"
-
-"@iconify/json@^2.1.147":
-  "integrity" "sha512-9DSzK1DIbrbjDK+LV8PTF/pTM8wJOrmRETEvVP47na093F6LEvcOQnq7Ett51e/WW0mA32GIj7LpO9opyJ/gtQ=="
-  "resolved" "https://registry.npmjs.org/@iconify/json/-/json-2.1.153.tgz"
-  "version" "2.1.153"
-  dependencies:
-    "@iconify/types" "*"
-    "pathe" "^0.3.0"
-
-"@iconify/types@*", "@iconify/types@^2.0.0":
-  "integrity" "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="
-  "resolved" "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz"
-  "version" "2.0.0"
-
-"@iconify/utils@^2.0.3":
-  "integrity" "sha512-/ZYdSK+vao8XPoRPkdQeetGBtuCcPcVTtuXUz2Y+281EpcmM/5G1SCbtgty28r/Y9HkR9ePbKst7eWUOK7/jNQ=="
-  "resolved" "https://registry.npmjs.org/@iconify/utils/-/utils-2.0.4.tgz"
-  "version" "2.0.4"
-  dependencies:
-    "@antfu/install-pkg" "^0.1.1"
-    "@antfu/utils" "^0.5.2"
-    "@iconify/types" "^2.0.0"
-    "debug" "^4.3.4"
-    "kolorist" "^1.6.0"
-    "local-pkg" "^0.4.2"
-
-"@jridgewell/sourcemap-codec@^1.4.13":
-  "integrity" "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
-  "resolved" "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
-  "version" "1.4.14"
-
-"@kurkle/color@^0.3.0":
-  "integrity" "sha512-hW0GwZj06z/ZFUW2Espl7toVDjghJN+EKqyXzPSV8NV89d5BYp5rRMBJoc+aUN0x5OXDMeRQHazejr2Xmqj2tw=="
-  "resolved" "https://registry.npmjs.org/@kurkle/color/-/color-0.3.1.tgz"
-  "version" "0.3.1"
-
-"@nodelib/fs.scandir@2.1.5":
-  "integrity" "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="
-  "resolved" "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
-  "version" "2.1.5"
-  dependencies:
-    "@nodelib/fs.stat" "2.0.5"
-    "run-parallel" "^1.1.9"
-
-"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
-  "integrity" "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
-  "resolved" "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
-  "version" "2.0.5"
-
-"@nodelib/fs.walk@^1.2.3":
-  "integrity" "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="
-  "resolved" "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
-  "version" "1.2.8"
-  dependencies:
-    "@nodelib/fs.scandir" "2.1.5"
-    "fastq" "^1.6.0"
-
-"@rollup/pluginutils@^5.0.2":
-  "integrity" "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA=="
-  "resolved" "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz"
-  "version" "5.0.2"
-  dependencies:
-    "@types/estree" "^1.0.0"
-    "estree-walker" "^2.0.2"
-    "picomatch" "^2.3.1"
-
-"@types/estree@^1.0.0":
-  "integrity" "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ=="
-  "resolved" "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz"
-  "version" "1.0.0"
-
-"@types/web-bluetooth@^0.0.16":
-  "integrity" "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ=="
-  "resolved" "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz"
-  "version" "0.0.16"
-
-"@vitejs/plugin-vue@^3.2.0":
-  "integrity" "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw=="
-  "resolved" "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz"
-  "version" "3.2.0"
-
-"@vue/compiler-core@3.2.45":
-  "integrity" "sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A=="
-  "resolved" "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@babel/parser" "^7.16.4"
-    "@vue/shared" "3.2.45"
-    "estree-walker" "^2.0.2"
-    "source-map" "^0.6.1"
-
-"@vue/compiler-dom@3.2.45":
-  "integrity" "sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw=="
-  "resolved" "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@vue/compiler-core" "3.2.45"
-    "@vue/shared" "3.2.45"
-
-"@vue/compiler-sfc@^3.0.2", "@vue/compiler-sfc@3.2.45":
-  "integrity" "sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q=="
-  "resolved" "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.45"
-    "@vue/compiler-dom" "3.2.45"
-    "@vue/compiler-ssr" "3.2.45"
-    "@vue/reactivity-transform" "3.2.45"
-    "@vue/shared" "3.2.45"
-    "estree-walker" "^2.0.2"
-    "magic-string" "^0.25.7"
-    "postcss" "^8.1.10"
-    "source-map" "^0.6.1"
-
-"@vue/compiler-ssr@3.2.45":
-  "integrity" "sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ=="
-  "resolved" "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@vue/compiler-dom" "3.2.45"
-    "@vue/shared" "3.2.45"
-
-"@vue/devtools-api@^6.4.5":
-  "integrity" "sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ=="
-  "resolved" "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.4.5.tgz"
-  "version" "6.4.5"
-
-"@vue/reactivity-transform@3.2.45":
-  "integrity" "sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ=="
-  "resolved" "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@babel/parser" "^7.16.4"
-    "@vue/compiler-core" "3.2.45"
-    "@vue/shared" "3.2.45"
-    "estree-walker" "^2.0.2"
-    "magic-string" "^0.25.7"
-
-"@vue/reactivity@3.2.45":
-  "integrity" "sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A=="
-  "resolved" "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@vue/shared" "3.2.45"
-
-"@vue/runtime-core@3.2.45":
-  "integrity" "sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A=="
-  "resolved" "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@vue/reactivity" "3.2.45"
-    "@vue/shared" "3.2.45"
-
-"@vue/runtime-dom@3.2.45":
-  "integrity" "sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA=="
-  "resolved" "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@vue/runtime-core" "3.2.45"
-    "@vue/shared" "3.2.45"
-    "csstype" "^2.6.8"
-
-"@vue/server-renderer@3.2.45":
-  "integrity" "sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g=="
-  "resolved" "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@vue/compiler-ssr" "3.2.45"
-    "@vue/shared" "3.2.45"
-
-"@vue/shared@3.2.45":
-  "integrity" "sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg=="
-  "resolved" "https://registry.npmjs.org/@vue/shared/-/shared-3.2.45.tgz"
-  "version" "3.2.45"
-
-"@vueuse/core@*", "@vueuse/core@^9.6.0":
-  "integrity" "sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A=="
-  "resolved" "https://registry.npmjs.org/@vueuse/core/-/core-9.6.0.tgz"
-  "version" "9.6.0"
-  dependencies:
-    "@types/web-bluetooth" "^0.0.16"
-    "@vueuse/metadata" "9.6.0"
-    "@vueuse/shared" "9.6.0"
-    "vue-demi" "*"
-
-"@vueuse/metadata@9.6.0":
-  "integrity" "sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w=="
-  "resolved" "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.6.0.tgz"
-  "version" "9.6.0"
-
-"@vueuse/shared@9.6.0":
-  "integrity" "sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ=="
-  "resolved" "https://registry.npmjs.org/@vueuse/shared/-/shared-9.6.0.tgz"
-  "version" "9.6.0"
-  dependencies:
-    "vue-demi" "*"
-
-"acorn-node@^1.8.2":
-  "integrity" "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A=="
-  "resolved" "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz"
-  "version" "1.8.2"
-  dependencies:
-    "acorn" "^7.0.0"
-    "acorn-walk" "^7.0.0"
-    "xtend" "^4.0.2"
-
-"acorn-walk@^7.0.0":
-  "integrity" "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA=="
-  "resolved" "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz"
-  "version" "7.2.0"
-
-"acorn@^7.0.0":
-  "integrity" "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="
-  "resolved" "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz"
-  "version" "7.4.1"
-
-"acorn@^8.8.1":
-  "integrity" "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA=="
-  "resolved" "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz"
-  "version" "8.8.1"
-
-"anymatch@~3.1.2":
-  "integrity" "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="
-  "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz"
-  "version" "3.1.3"
-  dependencies:
-    "normalize-path" "^3.0.0"
-    "picomatch" "^2.0.4"
-
-"arg@^5.0.2":
-  "integrity" "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
-  "resolved" "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz"
-  "version" "5.0.2"
-
-"asynckit@^0.4.0":
-  "integrity" "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
-  "resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz"
-  "version" "0.4.0"
-
-"autoprefixer@^10.4.13":
-  "integrity" "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg=="
-  "resolved" "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz"
-  "version" "10.4.13"
-  dependencies:
-    "browserslist" "^4.21.4"
-    "caniuse-lite" "^1.0.30001426"
-    "fraction.js" "^4.2.0"
-    "normalize-range" "^0.1.2"
-    "picocolors" "^1.0.0"
-    "postcss-value-parser" "^4.2.0"
-
-"axios@^1.2.0":
-  "integrity" "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A=="
-  "resolved" "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz"
-  "version" "1.2.1"
-  dependencies:
-    "follow-redirects" "^1.15.0"
-    "form-data" "^4.0.0"
-    "proxy-from-env" "^1.1.0"
-
-"balanced-match@^1.0.0":
-  "integrity" "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
-  "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
-  "version" "1.0.2"
-
-"binary-extensions@^2.0.0":
-  "integrity" "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA=="
-  "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
-  "version" "2.2.0"
-
-"brace-expansion@^2.0.1":
-  "integrity" "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="
-  "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz"
-  "version" "2.0.1"
-  dependencies:
-    "balanced-match" "^1.0.0"
-
-"braces@^3.0.2", "braces@~3.0.2":
-  "integrity" "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A=="
-  "resolved" "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz"
-  "version" "3.0.2"
-  dependencies:
-    "fill-range" "^7.0.1"
-
-"browserslist@^4.21.4", "browserslist@>= 4.21.0":
-  "integrity" "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw=="
-  "resolved" "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz"
-  "version" "4.21.4"
-  dependencies:
-    "caniuse-lite" "^1.0.30001400"
-    "electron-to-chromium" "^1.4.251"
-    "node-releases" "^2.0.6"
-    "update-browserslist-db" "^1.0.9"
-
-"camelcase-css@^2.0.1":
-  "integrity" "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
-  "resolved" "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz"
-  "version" "2.0.1"
-
-"caniuse-lite@^1.0.30001400", "caniuse-lite@^1.0.30001426":
-  "integrity" "sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw=="
-  "resolved" "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz"
-  "version" "1.0.30001512"
-
-"chart.js@^4.0.1", "chart.js@>=3.0.0", "chart.js@>=3.2.0":
-  "integrity" "sha512-Xta4vonV3e1pwFGLTyUMqXk2aH03D6253DSt52pdS7247u2SpIpQb6kCewxNgx1JfeLBUnjaxHYbHpOv6w78Og=="
-  "resolved" "https://registry.npmjs.org/chart.js/-/chart.js-4.1.0.tgz"
-  "version" "4.1.0"
-  dependencies:
-    "@kurkle/color" "^0.3.0"
-
-"chartjs-adapter-luxon@^1.3.0":
-  "integrity" "sha512-TPqS8S7aw4a07LhFzG5DZU6Kduk1xFkaGTn8y/gfhBRvfyCkqnwFqfXqG9Gl+gmnj5DRXgPscApJUE6bsgzKjQ=="
-  "resolved" "https://registry.npmjs.org/chartjs-adapter-luxon/-/chartjs-adapter-luxon-1.3.0.tgz"
-  "version" "1.3.0"
-
-"chartjs-plugin-autocolors@^0.0.7":
-  "integrity" "sha512-yAraaHD30d8/bXsFN/k8/Xq2cd2j8pctyd8v+mef58mEBr5RtTs1m5/3EJgB6RE+nxEOPMnZ8170siB4mmDP7g=="
-  "resolved" "https://registry.npmjs.org/chartjs-plugin-autocolors/-/chartjs-plugin-autocolors-0.0.7.tgz"
-  "version" "0.0.7"
-
-"chartjs-plugin-zoom@^2.0.0":
-  "integrity" "sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA=="
-  "resolved" "https://registry.npmjs.org/chartjs-plugin-zoom/-/chartjs-plugin-zoom-2.0.1.tgz"
-  "version" "2.0.1"
-  dependencies:
-    "hammerjs" "^2.0.8"
-
-"chokidar@^3.5.3":
-  "integrity" "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="
-  "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz"
-  "version" "3.5.3"
-  dependencies:
-    "anymatch" "~3.1.2"
-    "braces" "~3.0.2"
-    "glob-parent" "~5.1.2"
-    "is-binary-path" "~2.1.0"
-    "is-glob" "~4.0.1"
-    "normalize-path" "~3.0.0"
-    "readdirp" "~3.6.0"
-  optionalDependencies:
-    "fsevents" "~2.3.2"
-
-"color-name@^1.1.4":
-  "integrity" "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
-  "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
-  "version" "1.1.4"
-
-"combined-stream@^1.0.8":
-  "integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="
-  "resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
-  "version" "1.0.8"
-  dependencies:
-    "delayed-stream" "~1.0.0"
-
-"cross-spawn@^7.0.3":
-  "integrity" "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w=="
-  "resolved" "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz"
-  "version" "7.0.3"
-  dependencies:
-    "path-key" "^3.1.0"
-    "shebang-command" "^2.0.0"
-    "which" "^2.0.1"
-
-"cssesc@^3.0.0":
-  "integrity" "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
-  "resolved" "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
-  "version" "3.0.0"
-
-"csstype@^2.6.8":
-  "integrity" "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
-  "resolved" "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz"
-  "version" "2.6.21"
-
-"date-fns@^2.29.3":
-  "integrity" "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
-  "resolved" "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz"
-  "version" "2.29.3"
-
-"debug@^4.3.4":
-  "integrity" "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ=="
-  "resolved" "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
-  "version" "4.3.4"
-  dependencies:
-    "ms" "2.1.2"
-
-"defined@^1.0.0":
-  "integrity" "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q=="
-  "resolved" "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz"
-  "version" "1.0.1"
-
-"delayed-stream@~1.0.0":
-  "integrity" "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
-  "resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
-  "version" "1.0.0"
-
-"detective@^5.2.1":
-  "integrity" "sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw=="
-  "resolved" "https://registry.npmjs.org/detective/-/detective-5.2.1.tgz"
-  "version" "5.2.1"
-  dependencies:
-    "acorn-node" "^1.8.2"
-    "defined" "^1.0.0"
-    "minimist" "^1.2.6"
-
-"didyoumean@^1.2.2":
-  "integrity" "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
-  "resolved" "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz"
-  "version" "1.2.2"
-
-"dlv@^1.1.3":
-  "integrity" "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
-  "resolved" "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz"
-  "version" "1.1.3"
-
-"electron-to-chromium@^1.4.251":
-  "integrity" "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
-  "resolved" "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz"
-  "version" "1.4.284"
-
-"esbuild-windows-64@0.15.18":
-  "integrity" "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw=="
-  "resolved" "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz"
-  "version" "0.15.18"
-
-"esbuild@^0.15.9":
-  "integrity" "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q=="
-  "resolved" "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz"
-  "version" "0.15.18"
-  optionalDependencies:
-    "@esbuild/android-arm" "0.15.18"
-    "@esbuild/linux-loong64" "0.15.18"
-    "esbuild-android-64" "0.15.18"
-    "esbuild-android-arm64" "0.15.18"
-    "esbuild-darwin-64" "0.15.18"
-    "esbuild-darwin-arm64" "0.15.18"
-    "esbuild-freebsd-64" "0.15.18"
-    "esbuild-freebsd-arm64" "0.15.18"
-    "esbuild-linux-32" "0.15.18"
-    "esbuild-linux-64" "0.15.18"
-    "esbuild-linux-arm" "0.15.18"
-    "esbuild-linux-arm64" "0.15.18"
-    "esbuild-linux-mips64le" "0.15.18"
-    "esbuild-linux-ppc64le" "0.15.18"
-    "esbuild-linux-riscv64" "0.15.18"
-    "esbuild-linux-s390x" "0.15.18"
-    "esbuild-netbsd-64" "0.15.18"
-    "esbuild-openbsd-64" "0.15.18"
-    "esbuild-sunos-64" "0.15.18"
-    "esbuild-windows-32" "0.15.18"
-    "esbuild-windows-64" "0.15.18"
-    "esbuild-windows-arm64" "0.15.18"
-
-"escalade@^3.1.1":
-  "integrity" "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
-  "resolved" "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz"
-  "version" "3.1.1"
-
-"escape-string-regexp@^5.0.0":
-  "integrity" "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="
-  "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz"
-  "version" "5.0.0"
-
-"estree-walker@^2.0.2":
-  "integrity" "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
-  "resolved" "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz"
-  "version" "2.0.2"
-
-"execa@^5.1.1":
-  "integrity" "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="
-  "resolved" "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz"
-  "version" "5.1.1"
-  dependencies:
-    "cross-spawn" "^7.0.3"
-    "get-stream" "^6.0.0"
-    "human-signals" "^2.1.0"
-    "is-stream" "^2.0.0"
-    "merge-stream" "^2.0.0"
-    "npm-run-path" "^4.0.1"
-    "onetime" "^5.1.2"
-    "signal-exit" "^3.0.3"
-    "strip-final-newline" "^2.0.0"
-
-"fast-glob@^3.2.11", "fast-glob@^3.2.12":
-  "integrity" "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w=="
-  "resolved" "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz"
-  "version" "3.2.12"
-  dependencies:
-    "@nodelib/fs.stat" "^2.0.2"
-    "@nodelib/fs.walk" "^1.2.3"
-    "glob-parent" "^5.1.2"
-    "merge2" "^1.3.0"
-    "micromatch" "^4.0.4"
-
-"fastq@^1.6.0":
-  "integrity" "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg=="
-  "resolved" "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz"
-  "version" "1.14.0"
-  dependencies:
-    "reusify" "^1.0.4"
-
-"fill-range@^7.0.1":
-  "integrity" "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ=="
-  "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
-  "version" "7.0.1"
-  dependencies:
-    "to-regex-range" "^5.0.1"
-
-"find-up@^5.0.0":
-  "integrity" "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="
-  "resolved" "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
-  "version" "5.0.0"
-  dependencies:
-    "locate-path" "^6.0.0"
-    "path-exists" "^4.0.0"
-
-"follow-redirects@^1.15.0":
-  "integrity" "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
-  "resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz"
-  "version" "1.15.2"
-
-"form-data@^4.0.0":
-  "integrity" "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww=="
-  "resolved" "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz"
-  "version" "4.0.0"
-  dependencies:
-    "asynckit" "^0.4.0"
-    "combined-stream" "^1.0.8"
-    "mime-types" "^2.1.12"
-
-"fraction.js@^4.2.0":
-  "integrity" "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA=="
-  "resolved" "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz"
-  "version" "4.2.0"
-
-"function-bind@^1.1.1":
-  "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
-  "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
-  "version" "1.1.1"
-
-"get-stream@^6.0.0":
-  "integrity" "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="
-  "resolved" "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz"
-  "version" "6.0.1"
-
-"glob-parent@^5.1.2":
-  "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="
-  "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
-  "version" "5.1.2"
-  dependencies:
-    "is-glob" "^4.0.1"
-
-"glob-parent@^6.0.2":
-  "integrity" "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="
-  "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
-  "version" "6.0.2"
-  dependencies:
-    "is-glob" "^4.0.3"
-
-"glob-parent@~5.1.2":
-  "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="
-  "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
-  "version" "5.1.2"
-  dependencies:
-    "is-glob" "^4.0.1"
-
-"hammerjs@^2.0.8":
-  "integrity" "sha512-tSQXBXS/MWQOn/RKckawJ61vvsDpCom87JgxiYdGwHdOa0ht0vzUWDlfioofFCRU0L+6NGDt6XzbgoJvZkMeRQ=="
-  "resolved" "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz"
-  "version" "2.0.8"
-
-"has@^1.0.3":
-  "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw=="
-  "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz"
-  "version" "1.0.3"
-  dependencies:
-    "function-bind" "^1.1.1"
-
-"human-signals@^2.1.0":
-  "integrity" "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
-  "resolved" "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz"
-  "version" "2.1.0"
-
-"is-binary-path@~2.1.0":
-  "integrity" "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="
-  "resolved" "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz"
-  "version" "2.1.0"
-  dependencies:
-    "binary-extensions" "^2.0.0"
-
-"is-core-module@^2.9.0":
-  "integrity" "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw=="
-  "resolved" "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz"
-  "version" "2.11.0"
-  dependencies:
-    "has" "^1.0.3"
-
-"is-extglob@^2.1.1":
-  "integrity" "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
-  "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
-  "version" "2.1.1"
-
-"is-glob@^4.0.1", "is-glob@^4.0.3", "is-glob@~4.0.1":
-  "integrity" "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="
-  "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
-  "version" "4.0.3"
-  dependencies:
-    "is-extglob" "^2.1.1"
-
-"is-number@^7.0.0":
-  "integrity" "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
-  "resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
-  "version" "7.0.0"
-
-"is-stream@^2.0.0":
-  "integrity" "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
-  "resolved" "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz"
-  "version" "2.0.1"
-
-"isexe@^2.0.0":
-  "integrity" "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
-  "resolved" "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
-  "version" "2.0.0"
-
-"js-file-download@^0.4.12":
-  "integrity" "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg=="
-  "resolved" "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz"
-  "version" "0.4.12"
-
-"jsonc-parser@^3.2.0":
-  "integrity" "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w=="
-  "resolved" "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz"
-  "version" "3.2.0"
-
-"jwt-decode@^3.1.2":
-  "integrity" "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A=="
-  "resolved" "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz"
-  "version" "3.1.2"
-
-"kolorist@^1.6.0":
-  "integrity" "sha512-dLkz37Ab97HWMx9KTes3Tbi3D1ln9fCAy2zr2YVExJasDRPGRaKcoE4fycWNtnCAJfjFqe0cnY+f8KT2JePEXQ=="
-  "resolved" "https://registry.npmjs.org/kolorist/-/kolorist-1.6.0.tgz"
-  "version" "1.6.0"
-
-"leaflet@^1.9.3":
-  "integrity" "sha512-iB2cR9vAkDOu5l3HAay2obcUHZ7xwUBBjph8+PGtmW/2lYhbLizWtG7nTeYht36WfOslixQF9D/uSIzhZgGMfQ=="
-  "resolved" "https://registry.npmjs.org/leaflet/-/leaflet-1.9.3.tgz"
-  "version" "1.9.3"
-
-"lilconfig@^2.0.5", "lilconfig@^2.0.6":
-  "integrity" "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg=="
-  "resolved" "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz"
-  "version" "2.0.6"
-
-"local-pkg@^0.4.2":
-  "integrity" "sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg=="
-  "resolved" "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.2.tgz"
-  "version" "0.4.2"
-
-"locate-path@^6.0.0":
-  "integrity" "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="
-  "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
-  "version" "6.0.0"
-  dependencies:
-    "p-locate" "^5.0.0"
-
-"luxon@^3.1.1", "luxon@>=1.0.0":
-  "integrity" "sha512-Ah6DloGmvseB/pX1cAmjbFvyU/pKuwQMQqz7d0yvuDlVYLTs2WeDHQMpC8tGjm1da+BriHROW/OEIT/KfYg6xw=="
-  "resolved" "https://registry.npmjs.org/luxon/-/luxon-3.1.1.tgz"
-  "version" "3.1.1"
-
-"magic-string@^0.25.7":
-  "integrity" "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ=="
-  "resolved" "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz"
-  "version" "0.25.9"
-  dependencies:
-    "sourcemap-codec" "^1.4.8"
-
-"magic-string@^0.27.0":
-  "integrity" "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA=="
-  "resolved" "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz"
-  "version" "0.27.0"
-  dependencies:
-    "@jridgewell/sourcemap-codec" "^1.4.13"
-
-"merge-stream@^2.0.0":
-  "integrity" "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
-  "resolved" "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz"
-  "version" "2.0.0"
-
-"merge2@^1.3.0":
-  "integrity" "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
-  "resolved" "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
-  "version" "1.4.1"
-
-"micromatch@^4.0.4", "micromatch@^4.0.5":
-  "integrity" "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA=="
-  "resolved" "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz"
-  "version" "4.0.5"
-  dependencies:
-    "braces" "^3.0.2"
-    "picomatch" "^2.3.1"
-
-"mime-db@1.52.0":
-  "integrity" "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
-  "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
-  "version" "1.52.0"
-
-"mime-types@^2.1.12":
-  "integrity" "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="
-  "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz"
-  "version" "2.1.35"
-  dependencies:
-    "mime-db" "1.52.0"
-
-"mimic-fn@^2.1.0":
-  "integrity" "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
-  "resolved" "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz"
-  "version" "2.1.0"
-
-"minimatch@^5.1.1":
-  "integrity" "sha512-362NP+zlprccbEt/SkxKfRMHnNY85V74mVnpUpNyr3F35covl09Kec7/sEFLt3RA4oXmewtoaanoIf67SE5Y5g=="
-  "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-5.1.1.tgz"
-  "version" "5.1.1"
-  dependencies:
-    "brace-expansion" "^2.0.1"
-
-"minimist@^1.2.6":
-  "integrity" "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g=="
-  "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz"
-  "version" "1.2.7"
-
-"mitt@^3.0.0":
-  "integrity" "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ=="
-  "resolved" "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz"
-  "version" "3.0.0"
-
-"mlly@^1.0.0":
-  "integrity" "sha512-QL108Hwt+u9bXdWgOI0dhzZfACovn5Aen4Xvc8Jasd9ouRH4NjnrXEiyP3nVvJo91zPlYjVRckta0Nt2zfoR6g=="
-  "resolved" "https://registry.npmjs.org/mlly/-/mlly-1.0.0.tgz"
-  "version" "1.0.0"
-  dependencies:
-    "acorn" "^8.8.1"
-    "pathe" "^1.0.0"
-    "pkg-types" "^1.0.0"
-    "ufo" "^1.0.0"
-
-"ms@2.1.2":
-  "integrity" "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
-  "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
-  "version" "2.1.2"
-
-"nanoid@^3.3.4":
-  "integrity" "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
-  "resolved" "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz"
-  "version" "3.3.4"
-
-"node-releases@^2.0.6":
-  "integrity" "sha512-EJ3rzxL9pTWPjk5arA0s0dgXpnyiAbJDE6wHT62g7VsgrgQgmmZ+Ru++M1BFofncWja+Pnn3rEr3fieRySAdKQ=="
-  "resolved" "https://registry.npmjs.org/node-releases/-/node-releases-2.0.7.tgz"
-  "version" "2.0.7"
-
-"normalize-path@^3.0.0", "normalize-path@~3.0.0":
-  "integrity" "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
-  "resolved" "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz"
-  "version" "3.0.0"
-
-"normalize-range@^0.1.2":
-  "integrity" "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="
-  "resolved" "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz"
-  "version" "0.1.2"
-
-"npm-run-path@^4.0.1":
-  "integrity" "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="
-  "resolved" "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz"
-  "version" "4.0.1"
-  dependencies:
-    "path-key" "^3.0.0"
-
-"object-hash@^3.0.0":
-  "integrity" "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="
-  "resolved" "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz"
-  "version" "3.0.0"
-
-"onetime@^5.1.2":
-  "integrity" "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="
-  "resolved" "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz"
-  "version" "5.1.2"
-  dependencies:
-    "mimic-fn" "^2.1.0"
-
-"p-limit@^3.0.2":
-  "integrity" "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="
-  "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
-  "version" "3.1.0"
-  dependencies:
-    "yocto-queue" "^0.1.0"
-
-"p-locate@^5.0.0":
-  "integrity" "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="
-  "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz"
-  "version" "5.0.0"
-  dependencies:
-    "p-limit" "^3.0.2"
-
-"path-exists@^4.0.0":
-  "integrity" "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
-  "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
-  "version" "4.0.0"
-
-"path-key@^3.0.0", "path-key@^3.1.0":
-  "integrity" "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
-  "resolved" "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
-  "version" "3.1.1"
-
-"path-parse@^1.0.7":
-  "integrity" "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
-  "resolved" "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz"
-  "version" "1.0.7"
-
-"pathe@^0.3.0":
-  "integrity" "sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g=="
-  "resolved" "https://registry.npmjs.org/pathe/-/pathe-0.3.9.tgz"
-  "version" "0.3.9"
-
-"pathe@^1.0.0":
-  "integrity" "sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg=="
-  "resolved" "https://registry.npmjs.org/pathe/-/pathe-1.0.0.tgz"
-  "version" "1.0.0"
-
-"picocolors@^1.0.0":
-  "integrity" "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
-  "resolved" "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
-  "version" "1.0.0"
-
-"picomatch@^2.0.4", "picomatch@^2.2.1", "picomatch@^2.3.1":
-  "integrity" "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
-  "resolved" "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
-  "version" "2.3.1"
-
-"pify@^2.3.0":
-  "integrity" "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
-  "resolved" "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
-  "version" "2.3.0"
-
-"pkg-types@^1.0.0", "pkg-types@^1.0.1":
-  "integrity" "sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g=="
-  "resolved" "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.1.tgz"
-  "version" "1.0.1"
-  dependencies:
-    "jsonc-parser" "^3.2.0"
-    "mlly" "^1.0.0"
-    "pathe" "^1.0.0"
-
-"postcss-import@^14.1.0":
-  "integrity" "sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw=="
-  "resolved" "https://registry.npmjs.org/postcss-import/-/postcss-import-14.1.0.tgz"
-  "version" "14.1.0"
-  dependencies:
-    "postcss-value-parser" "^4.0.0"
-    "read-cache" "^1.0.0"
-    "resolve" "^1.1.7"
-
-"postcss-js@^4.0.0":
-  "integrity" "sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ=="
-  "resolved" "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.0.tgz"
-  "version" "4.0.0"
-  dependencies:
-    "camelcase-css" "^2.0.1"
-
-"postcss-load-config@^3.1.4":
-  "integrity" "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="
-  "resolved" "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz"
-  "version" "3.1.4"
-  dependencies:
-    "lilconfig" "^2.0.5"
-    "yaml" "^1.10.2"
-
-"postcss-nested@6.0.0":
-  "integrity" "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w=="
-  "resolved" "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz"
-  "version" "6.0.0"
-  dependencies:
-    "postcss-selector-parser" "^6.0.10"
-
-"postcss-selector-parser@^6.0.10":
-  "integrity" "sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g=="
-  "resolved" "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.11.tgz"
-  "version" "6.0.11"
-  dependencies:
-    "cssesc" "^3.0.0"
-    "util-deprecate" "^1.0.2"
-
-"postcss-value-parser@^4.0.0", "postcss-value-parser@^4.2.0":
-  "integrity" "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
-  "resolved" "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
-  "version" "4.2.0"
-
-"postcss@^8.0.0", "postcss@^8.1.0", "postcss@^8.1.10", "postcss@^8.2.14", "postcss@^8.3.3", "postcss@^8.4.18", "postcss@^8.4.19", "postcss@>=8.0.9":
-  "integrity" "sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g=="
-  "resolved" "https://registry.npmjs.org/postcss/-/postcss-8.4.20.tgz"
-  "version" "8.4.20"
-  dependencies:
-    "nanoid" "^3.3.4"
-    "picocolors" "^1.0.0"
-    "source-map-js" "^1.0.2"
-
-"proxy-from-env@^1.1.0":
-  "integrity" "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
-  "resolved" "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
-  "version" "1.1.0"
-
-"queue-microtask@^1.2.2":
-  "integrity" "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
-  "resolved" "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
-  "version" "1.2.3"
-
-"quick-lru@^5.1.1":
-  "integrity" "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
-  "resolved" "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
-  "version" "5.1.1"
-
-"read-cache@^1.0.0":
-  "integrity" "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="
-  "resolved" "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz"
-  "version" "1.0.0"
-  dependencies:
-    "pify" "^2.3.0"
-
-"readdirp@~3.6.0":
-  "integrity" "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="
-  "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz"
-  "version" "3.6.0"
-  dependencies:
-    "picomatch" "^2.2.1"
-
-"resolve@^1.1.7", "resolve@^1.22.1":
-  "integrity" "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw=="
-  "resolved" "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz"
-  "version" "1.22.1"
-  dependencies:
-    "is-core-module" "^2.9.0"
-    "path-parse" "^1.0.7"
-    "supports-preserve-symlinks-flag" "^1.0.0"
-
-"reusify@^1.0.4":
-  "integrity" "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
-  "resolved" "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
-  "version" "1.0.4"
-
-"rollup@^1.20.0||^2.0.0||^3.0.0", "rollup@^2.79.1":
-  "integrity" "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw=="
-  "resolved" "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz"
-  "version" "2.79.1"
-  optionalDependencies:
-    "fsevents" "~2.3.2"
-
-"run-parallel@^1.1.9":
-  "integrity" "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="
-  "resolved" "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
-  "version" "1.2.0"
-  dependencies:
-    "queue-microtask" "^1.2.2"
-
-"scule@^1.0.0":
-  "integrity" "sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ=="
-  "resolved" "https://registry.npmjs.org/scule/-/scule-1.0.0.tgz"
-  "version" "1.0.0"
-
-"shebang-command@^2.0.0":
-  "integrity" "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="
-  "resolved" "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
-  "version" "2.0.0"
-  dependencies:
-    "shebang-regex" "^3.0.0"
-
-"shebang-regex@^3.0.0":
-  "integrity" "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
-  "resolved" "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
-  "version" "3.0.0"
-
-"signal-exit@^3.0.3":
-  "integrity" "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
-  "resolved" "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz"
-  "version" "3.0.7"
-
-"source-map-js@^1.0.2":
-  "integrity" "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw=="
-  "resolved" "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
-  "version" "1.0.2"
-
-"source-map@^0.6.1":
-  "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
-  "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
-  "version" "0.6.1"
-
-"sourcemap-codec@^1.4.8":
-  "integrity" "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
-  "resolved" "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz"
-  "version" "1.4.8"
-
-"strip-final-newline@^2.0.0":
-  "integrity" "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
-  "resolved" "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz"
-  "version" "2.0.0"
-
-"strip-literal@^1.0.0":
-  "integrity" "sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ=="
-  "resolved" "https://registry.npmjs.org/strip-literal/-/strip-literal-1.0.0.tgz"
-  "version" "1.0.0"
-  dependencies:
-    "acorn" "^8.8.1"
-
-"supports-preserve-symlinks-flag@^1.0.0":
-  "integrity" "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
-  "resolved" "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
-  "version" "1.0.0"
-
-"tailwind-nord@^1.2.0":
-  "integrity" "sha512-8CsntjXDV0P+5jGhr5l5fbFPwEzxODebitSn2kQi8hW4jGYISh2WiIUkz+PRJgboaMgij9SUdI6uaWFHRNoNmw=="
-  "resolved" "https://registry.npmjs.org/tailwind-nord/-/tailwind-nord-1.2.0.tgz"
-  "version" "1.2.0"
-
-"tailwindcss@^3.2.4":
-  "integrity" "sha512-AhwtHCKMtR71JgeYDaswmZXhPcW9iuI9Sp2LvZPo9upDZ7231ZJ7eA9RaURbhpXGVlrjX4cFNlB4ieTetEb7hQ=="
-  "resolved" "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.4.tgz"
-  "version" "3.2.4"
-  dependencies:
-    "arg" "^5.0.2"
-    "chokidar" "^3.5.3"
-    "color-name" "^1.1.4"
-    "detective" "^5.2.1"
-    "didyoumean" "^1.2.2"
-    "dlv" "^1.1.3"
-    "fast-glob" "^3.2.12"
-    "glob-parent" "^6.0.2"
-    "is-glob" "^4.0.3"
-    "lilconfig" "^2.0.6"
-    "micromatch" "^4.0.5"
-    "normalize-path" "^3.0.0"
-    "object-hash" "^3.0.0"
-    "picocolors" "^1.0.0"
-    "postcss" "^8.4.18"
-    "postcss-import" "^14.1.0"
-    "postcss-js" "^4.0.0"
-    "postcss-load-config" "^3.1.4"
-    "postcss-nested" "6.0.0"
-    "postcss-selector-parser" "^6.0.10"
-    "postcss-value-parser" "^4.2.0"
-    "quick-lru" "^5.1.1"
-    "resolve" "^1.22.1"
-
-"to-regex-range@^5.0.1":
-  "integrity" "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="
-  "resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
-  "version" "5.0.1"
-  dependencies:
-    "is-number" "^7.0.0"
-
-"ufo@^1.0.0":
-  "integrity" "sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA=="
-  "resolved" "https://registry.npmjs.org/ufo/-/ufo-1.0.1.tgz"
-  "version" "1.0.1"
-
-"unimport@^1.0.2":
-  "integrity" "sha512-DcYkDwl1XMZNmyEKUFzVzHAul0FZcj9m0OM/WRfaAtg6Gw1waYlypYJl6qAg31k57TnNPwGDCAxYPodYC5qomQ=="
-  "resolved" "https://registry.npmjs.org/unimport/-/unimport-1.0.2.tgz"
-  "version" "1.0.2"
-  dependencies:
-    "@rollup/pluginutils" "^5.0.2"
-    "escape-string-regexp" "^5.0.0"
-    "fast-glob" "^3.2.12"
-    "local-pkg" "^0.4.2"
-    "magic-string" "^0.27.0"
-    "mlly" "^1.0.0"
-    "pathe" "^1.0.0"
-    "pkg-types" "^1.0.1"
-    "scule" "^1.0.0"
-    "strip-literal" "^1.0.0"
-    "unplugin" "^1.0.1"
-
-"unplugin-auto-import@^0.12.0":
-  "integrity" "sha512-J/3ZORq5YGKG+8D5vLLOgqaHNK77izlVN07mQ752yRLqBNDbJiwPRSnUwwYqH5N6rDay1SqnJCHaUdbJ9QMI2w=="
-  "resolved" "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.12.1.tgz"
-  "version" "0.12.1"
-  dependencies:
-    "@antfu/utils" "^0.7.2"
-    "@rollup/pluginutils" "^5.0.2"
-    "local-pkg" "^0.4.2"
-    "magic-string" "^0.27.0"
-    "unimport" "^1.0.2"
-    "unplugin" "^1.0.1"
-
-"unplugin-icons@^0.14.14":
-  "integrity" "sha512-J6YBA+fUzVM2IZPXCK3Pnk36jYVwQ6lkjRgOnZaXNIxpMDsmwDqrE1AGJ0zUbfuEoOa90OBGc0OPfN1r+qlSIQ=="
-  "resolved" "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.14.15.tgz"
-  "version" "0.14.15"
-  dependencies:
-    "@antfu/install-pkg" "^0.1.1"
-    "@antfu/utils" "^0.7.2"
-    "@iconify/utils" "^2.0.3"
-    "debug" "^4.3.4"
-    "kolorist" "^1.6.0"
-    "local-pkg" "^0.4.2"
-    "unplugin" "^1.0.1"
-
-"unplugin-vue-components@^0.22.11":
-  "integrity" "sha512-FxyzsuBvMCYPIk+8cgscGBQ345tvwVu+qY5IhE++eorkyvA4Z1TiD/HCiim+Kbqozl10i4K+z+NCa2WO2jexRA=="
-  "resolved" "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.22.12.tgz"
-  "version" "0.22.12"
-  dependencies:
-    "@antfu/utils" "^0.7.2"
-    "@rollup/pluginutils" "^5.0.2"
-    "chokidar" "^3.5.3"
-    "debug" "^4.3.4"
-    "fast-glob" "^3.2.12"
-    "local-pkg" "^0.4.2"
-    "magic-string" "^0.27.0"
-    "minimatch" "^5.1.1"
-    "resolve" "^1.22.1"
-    "unplugin" "^1.0.1"
-
-"unplugin@^1.0.1":
-  "integrity" "sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA=="
-  "resolved" "https://registry.npmjs.org/unplugin/-/unplugin-1.0.1.tgz"
-  "version" "1.0.1"
-  dependencies:
-    "acorn" "^8.8.1"
-    "chokidar" "^3.5.3"
-    "webpack-sources" "^3.2.3"
-    "webpack-virtual-modules" "^0.5.0"
-
-"update-browserslist-db@^1.0.9":
-  "integrity" "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ=="
-  "resolved" "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz"
-  "version" "1.0.10"
-  dependencies:
-    "escalade" "^3.1.1"
-    "picocolors" "^1.0.0"
-
-"util-deprecate@^1.0.2":
-  "integrity" "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
-  "resolved" "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
-  "version" "1.0.2"
-
-"vanillajs-datepicker@^1.2.0":
-  "integrity" "sha512-bfGdPYMf/lPwx0bJaXKKeyr3J7TnSBzB8xRWxMq6rtLxyAyyAjdCQ1h0WRGpRmE0qXIKdDy3yzRMDLHEGOub3A=="
-  "resolved" "https://registry.npmjs.org/vanillajs-datepicker/-/vanillajs-datepicker-1.2.0.tgz"
-  "version" "1.2.0"
-
-"vite-plugin-fonts@^0.6.0":
-  "integrity" "sha512-dV6nnLEju8k5EmvlBH6egxkVZ+rgc5zWsJr9+cNRXBMEDnpRGHcZPI260UEDNg2yB99wSTNER2eduEvZFbMIGw=="
-  "resolved" "https://registry.npmjs.org/vite-plugin-fonts/-/vite-plugin-fonts-0.6.0.tgz"
-  "version" "0.6.0"
-  dependencies:
-    "fast-glob" "^3.2.11"
-
-"vite@^2.0.0 || ^3.0.0", "vite@^3.0.0", "vite@^3.2.4":
-  "integrity" "sha512-4mVEpXpSOgrssFZAOmGIr85wPHKvaDAcXqxVxVRZhljkJOMZi1ibLibzjLHzJvcok8BMguLc7g1W6W/GqZbLdQ=="
-  "resolved" "https://registry.npmjs.org/vite/-/vite-3.2.5.tgz"
-  "version" "3.2.5"
-  dependencies:
-    "esbuild" "^0.15.9"
-    "postcss" "^8.4.18"
-    "resolve" "^1.22.1"
-    "rollup" "^2.79.1"
-  optionalDependencies:
-    "fsevents" "~2.3.2"
-
-"vue-demi@*":
-  "integrity" "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A=="
-  "resolved" "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz"
-  "version" "0.13.11"
-
-"vue-router@4.1.6":
-  "integrity" "sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ=="
-  "resolved" "https://registry.npmjs.org/vue-router/-/vue-router-4.1.6.tgz"
-  "version" "4.1.6"
-  dependencies:
-    "@vue/devtools-api" "^6.4.5"
-
-"vue@^3.0.0-0 || ^2.6.0", "vue@^3.2.0", "vue@^3.2.25", "vue@^3.2.45", "vue@2 || 3", "vue@3.2.45":
-  "integrity" "sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA=="
-  "resolved" "https://registry.npmjs.org/vue/-/vue-3.2.45.tgz"
-  "version" "3.2.45"
-  dependencies:
-    "@vue/compiler-dom" "3.2.45"
-    "@vue/compiler-sfc" "3.2.45"
-    "@vue/runtime-dom" "3.2.45"
-    "@vue/server-renderer" "3.2.45"
-    "@vue/shared" "3.2.45"
-
-"webpack-sources@^3.2.3":
-  "integrity" "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w=="
-  "resolved" "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz"
-  "version" "3.2.3"
-
-"webpack-virtual-modules@^0.5.0":
-  "integrity" "sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw=="
-  "resolved" "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz"
-  "version" "0.5.0"
-
-"which@^2.0.1":
-  "integrity" "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="
-  "resolved" "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
-  "version" "2.0.2"
-  dependencies:
-    "isexe" "^2.0.0"
-
-"xtend@^4.0.2":
-  "integrity" "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
-  "resolved" "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
-  "version" "4.0.2"
-
-"yaml@^1.10.2":
-  "integrity" "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
-  "resolved" "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz"
-  "version" "1.10.2"
-
-"yocto-queue@^0.1.0":
-  "integrity" "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
-  "resolved" "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
-  "version" "0.1.0"
diff --git a/cron/refresh_views.py b/cron/refresh_views.py
new file mode 100644
index 0000000..2e2f6cd
--- /dev/null
+++ b/cron/refresh_views.py
@@ -0,0 +1,25 @@
+import psycopg2
+import os
+from dotenv import load_dotenv
+
+
+basedir = os.path.abspath(os.path.dirname(__file__))
+load_dotenv(os.path.join(basedir, '../.env'))
+
+try:
+    conn = psycopg2.connect(os.environ.get('DB_URI'))
+except:
+    print("I am unable to connect to the database")
+
+with conn.cursor() as curs:
+
+    try:
+        # Run function to refresh materialized views
+        print("Refreshing materialized views. This may take a while...")
+        curs.execute("select raven_refresh_aggregates()")
+        print("Done")
+    except (Exception, psycopg2.DatabaseError) as error:
+        print(error)
+
+conn.commit()
+curs.close()
diff --git a/sql/pre_aggregates.sql b/sql/pre_aggregates.sql
new file mode 100644
index 0000000..912c3ff
--- /dev/null
+++ b/sql/pre_aggregates.sql
@@ -0,0 +1,325 @@
+------------------------------------------------------------------------------------
+
+create index idx_obs_spoid_day on public.observations (sampling_point_id, date_trunc('year'::text, from_time));
+create index idx_obs_spoid_day on public.observations (sampling_point_id, date_trunc('day'::text, from_time));
+VACUUM FULL ANALYZE observations;
+
+------------------------------------------------------------------------------------
+
+create or replace function raven_coverage(datetime timestamp,count integer, timestep integer,coverage_type text default 'year')
+returns numeric(10) as $$
+declare y integer;
+declare is_leap_year boolean;
+declare seconds integer;
+begin
+    seconds := 31536000;
+    y := extract(year from datetime);
+    is_leap_year := (y % 4 = 0) and (y % 100 <> 0 or y % 400 = 0);
+
+    if is_leap_year then
+        seconds := 31622400;
+    end if;
+
+    if coverage_type = 'aot40v' then
+        seconds := 3974400;
+    elsif coverage_type = 'aot40f' then
+        seconds :=  7905600;
+    elsif coverage_type = 'winterseason' and not is_leap_year then
+        seconds := 15724800;
+    elsif coverage_type = 'winterseason' and  is_leap_year then
+        seconds := 15811200;
+    elsif coverage_type = 'summeryear' then
+        seconds := 15811200;
+    elsif coverage_type = 'winteryear' and not is_leap_year then
+        seconds := 15724800;
+    elsif coverage_type = 'winteryear' and  is_leap_year then
+        seconds := 15811200;
+    elsif coverage_type = 'day' then
+        seconds := 86400;
+    end if;
+
+    return round((count::numeric*100) / (seconds/timestep),10);
+end
+$$ language plpgsql;
+
+
+------------------------------------------------------------------------------------
+
+
+create or replace function raven_refresh_aggregates()
+returns void as $$
+begin
+    REFRESH MATERIALIZED VIEW  CONCURRENTLY  observations_year;
+    REFRESH MATERIALIZED VIEW  CONCURRENTLY  observations_aot40f;
+    REFRESH MATERIALIZED VIEW  CONCURRENTLY  observations_aot40v;
+    REFRESH MATERIALIZED VIEW  CONCURRENTLY  observations_winter_season;
+    REFRESH MATERIALIZED VIEW  CONCURRENTLY  observations_summer_year;
+    REFRESH MATERIALIZED VIEW  CONCURRENTLY  observations_winter_year;
+    REFRESH MATERIALIZED VIEW  CONCURRENTLY  observations_day;
+end
+$$ language plpgsql;
+
+
+
+------------------------------------------------------------------------------------
+
+create materialized view observations_year as
+with timeseries as (select s.id as sampling_point_id, t.timestep from sampling_points s, eea_times t WHERE s.timestep = t.id)
+select
+    a.*,
+    raven_coverage(time,count_valid,t.timestep) as cov,
+    now() as created
+from (select sampling_point_id,
+             date_trunc('year', from_time)                                     as time,
+             round(avg(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as val,
+             round(min(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as min,
+             round(max(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as max,
+             count(value)          ::int                                            AS count_all,
+             count(value) FILTER (WHERE validation_flag in (1, 2, 3)) ::int         AS count_valid,
+             count(*) FILTER (WHERE verification_flag = 1)  ::int                   as count_verified
+      from observations
+      group by sampling_point_id, time
+) a, timeseries t
+where a.sampling_point_id = t.sampling_point_id;
+
+
+CREATE INDEX idx_obs_year_id_time
+on observations_year (sampling_point_id,time);
+
+CREATE UNIQUE INDEX un_obs_year_id_time
+on observations_year (sampling_point_id,time);
+
+VACUUM FULL ANALYZE observations_year;
+
+------------------------------------------------------------------------------------
+
+create materialized view observations_winter_year as
+with timeseries as (select s.id as sampling_point_id, t.timestep from sampling_points s, eea_times t WHERE s.timestep = t.id)
+select
+    a.*,
+    raven_coverage(a.time, a.count_valid::int,t.timestep,'winteryear') as cov,
+    now() as created
+from (
+    select
+        sampling_point_id,
+        date_trunc('year', from_time)                                     as time,
+        round(avg(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as val,
+        round(min(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as min,
+        round(max(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as max,
+        count(value)          ::int                                            AS count_all,
+        count(value) FILTER (WHERE validation_flag in (1, 2, 3)) ::int         AS count_valid,
+        count(*) FILTER (WHERE verification_flag = 1)  ::int                   as count_verified
+    from observations
+    where 1 = 1
+    and to_char(from_time, 'MM') IN ('01','02','03','10','11','12')
+    group by sampling_point_id, time
+) a, timeseries t
+where a.sampling_point_id = t.sampling_point_id;
+
+
+CREATE INDEX idx_obs_winter_year_id_time
+on observations_winter_year (sampling_point_id,time);
+
+CREATE UNIQUE INDEX un_obs_winter_year_id_time
+on observations_winter_year (sampling_point_id,time);
+
+VACUUM FULL ANALYZE observations_winter_year;
+
+------------------------------------------------------------------------------------
+
+create materialized view observations_winter_season as
+with
+    timeseries as (select s.id as sampling_point_id, t.timestep from sampling_points s, eea_times t WHERE s.timestep = t.id),
+    timevalues as
+         (SELECT CASE EXTRACT(MONTH FROM o.from_time)
+                     WHEN 10 THEN (o.from_time + interval '12 MONTH')
+                     WHEN 11 THEN (o.from_time + interval '12 MONTH')
+                     WHEN 12 THEN (o.from_time + interval '12 MONTH')
+                     ELSE o.from_time
+                     END as from_time,
+                    o.sampling_point_id,
+                    o.value,
+                    o.validation_flag,
+                    o.verification_flag
+          FROM observations o
+          WHERE EXTRACT(MONTH FROM o.from_time) IN (1, 2, 3, 10, 11, 12))
+select
+    a.*,
+    raven_coverage(time,count_valid,t.timestep,'winterseason') as cov,
+    now() as created
+from (
+select
+    sampling_point_id,
+    date_trunc('year', from_time) as time,
+    round(avg(value) FILTER (WHERE validation_flag in (1,2,3)), 10) as val,
+    round(min(value) FILTER (WHERE validation_flag in (1,2,3)), 10) as min,
+    round(max(value) FILTER (WHERE validation_flag in (1,2,3)), 10) as max,
+    count(value)::int AS count_all,
+    count(value) FILTER (WHERE validation_flag in (1,2,3))::int AS count_valid,
+    count(*) FILTER (WHERE verification_flag = 1)::int as count_verified
+from timevalues
+group by  sampling_point_id, time
+) a, timeseries t
+where a.sampling_point_id = t.sampling_point_id;
+
+CREATE INDEX idx_obs_winter_season_id_time
+on observations_winter_season (sampling_point_id,time);
+
+
+CREATE UNIQUE INDEX un_obs_winter_season_id_time
+on observations_winter_season (sampling_point_id,time);
+
+VACUUM FULL ANALYZE observations_winter_season;
+
+------------------------------------------------------------------------------------
+
+create materialized view observations_summer_year as
+with timeseries as (select s.id as sampling_point_id, t.timestep from sampling_points s, eea_times t WHERE s.timestep = t.id)
+select
+    a.*,
+    raven_coverage(a.time, a.count_valid::int,t.timestep,'summeryear') as cov,
+    now() as created
+from (
+    select
+        sampling_point_id,
+        date_trunc('year', from_time)                                     as time,
+        round(avg(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as val,
+        round(min(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as min,
+        round(max(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as max,
+        count(value)          ::int                                            AS count_all,
+        count(value) FILTER (WHERE validation_flag in (1, 2, 3)) ::int         AS count_valid,
+        count(*) FILTER (WHERE verification_flag = 1)  ::int                   as count_verified
+    from observations
+    where 1 = 1
+    and to_char(from_time, 'MM') IN ('04', '05', '06', '07', '08', '09')
+    group by sampling_point_id, time
+) a, timeseries t
+where a.sampling_point_id = t.sampling_point_id;
+
+
+CREATE INDEX idx_obs_summer_year_id_time
+on observations_summer_year(sampling_point_id,time);
+
+CREATE UNIQUE INDEX un_obs_summer_year_id_time
+on observations_summer_year (sampling_point_id,time);
+
+VACUUM FULL ANALYZE observations_summer_year;
+
+------------------------------------------------------------------------------------
+
+create materialized view observations_day as
+with timeseries as (select s.id as sampling_point_id, t.timestep from sampling_points s, eea_times t WHERE s.timestep = t.id)
+select
+    a.*,
+    raven_coverage(time,count_valid,t.timestep, 'day') as cov,
+    now() as created
+from
+(
+    select
+        sampling_point_id,
+        date_trunc('day', from_time) as time,
+        round(avg(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as val,
+        round(min(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as min,
+        round(max(value) FILTER (WHERE validation_flag in (1, 2, 3)), 10) as max,
+        count(value)::int AS count_all,
+        count(value) FILTER (WHERE validation_flag in (1, 2, 3)) ::int AS count_valid,
+        count(*) FILTER (WHERE verification_flag = 1)  ::int as count_verified
+      from observations
+      group by sampling_point_id, time
+) a, timeseries t
+where a.sampling_point_id = t.sampling_point_id;
+
+CREATE INDEX idx_obs_day_id_time
+on observations_day (sampling_point_id,time);
+
+CREATE UNIQUE INDEX un_obs_day_id_time
+on observations_day (sampling_point_id,time);
+
+VACUUM FULL ANALYZE observations_day;
+
+
+------------------------------------------------------------------------------------
+
+create materialized view observations_aot40v as
+with timeseries as (select s.id as sampling_point_id, t.timestep from sampling_points s, eea_times t WHERE s.timestep = t.id)
+select
+    a.sampling_point_id,
+    a.time,
+    case when raven_coverage(a.time, a.count_valid,t.timestep,'aot40v') < 100 then round(a.val*count_all/count_valid,10) else a.val  end as val,
+    a.min,
+    a.max,
+    a.count_all,
+    a.count_valid,
+    a.count_verified,
+    raven_coverage(a.time, a.count_valid::int,t.timestep,'aot40v') as cov,
+    now() as created
+from (
+    select
+        sampling_point_id,
+        date_trunc('year', from_time)                                                             as time,
+        round(sum(value - 80) FILTER (WHERE validation_flag in (1, 2, 3) AND value - 80 > 0), 10) as val,
+        round(min(value) FILTER (WHERE validation_flag in (1, 2, 3) AND value - 80 > 0), 10)      as min,
+        round(max(value) FILTER (WHERE validation_flag in (1, 2, 3) AND value - 80 > 0), 10)      as max,
+        count(value)::int                                                                              AS count_all,
+        count(value) FILTER (WHERE validation_flag in (1, 2, 3))::int               AS count_valid,
+        count(*) FILTER (WHERE verification_flag = 1)::int                                             as count_verified
+    from observations
+    where 1 = 1
+    and to_char(from_time, 'MM') IN ('05', '06', '07')
+    and to_char(from_time, 'HH24') IN ('08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19')
+    group by sampling_point_id, time
+) a, timeseries t
+where a.sampling_point_id = t.sampling_point_id;
+
+
+CREATE INDEX idx_obs_aot40v_id_time
+on observations_aot40v (sampling_point_id,time);
+
+CREATE UNIQUE INDEX un_obs_aot40v_id_time
+on observations_aot40v (sampling_point_id,time);
+
+VACUUM FULL ANALYZE observations_aot40v;
+
+------------------------------------------------------------------------------------
+
+create materialized view observations_aot40f as
+with timeseries as (select s.id as sampling_point_id, t.timestep from sampling_points s, eea_times t WHERE s.timestep = t.id)
+select
+    a.sampling_point_id,
+    a.time,
+    case when raven_coverage(a.time, a.count_valid,t.timestep,'aot40f') < 100 then round(a.val*count_all/count_valid,10) else a.val  end as val,
+    a.min,
+    a.max,
+    a.count_all,
+    a.count_valid,
+    a.count_verified,
+    raven_coverage(a.time, a.count_valid::int,t.timestep,'aot40f') as cov,
+    now() as created
+from (
+    select
+        sampling_point_id,
+        date_trunc('year', from_time)                                                             as time,
+        round(sum(value - 80) FILTER (WHERE validation_flag in (1, 2, 3) AND value - 80 > 0), 10) as val,
+        round(min(value) FILTER (WHERE validation_flag in (1, 2, 3) AND value - 80 > 0), 10)      as min,
+        round(max(value) FILTER (WHERE validation_flag in (1, 2, 3) AND value - 80 > 0), 10)      as max,
+        count(value)::int                                                                              AS count_all,
+        count(value) FILTER (WHERE validation_flag in (1, 2, 3))::int               AS count_valid,
+        count(*) FILTER (WHERE verification_flag = 1)::int                                             as count_verified
+    from observations
+    where 1 = 1
+    and to_char(from_time, 'MM') IN ('04', '05', '06', '07', '08', '09')
+    and to_char(from_time, 'HH24') IN ('08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19')
+    group by sampling_point_id, time
+) a, timeseries t
+where a.sampling_point_id = t.sampling_point_id;
+
+
+CREATE INDEX idx_obs_aot40f_id_time
+on observations_aot40f (sampling_point_id,time);
+
+CREATE UNIQUE INDEX un_obs_aot40f_id_time
+on observations_aot40f (sampling_point_id,time);
+
+VACUUM FULL ANALYZE observations_aot40f;
+
+------------------------------------------------------------------------------------
\ No newline at end of file
-- 
GitLab


From 6c3fb0bff799371f801b45dc88626b953a20c311 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 1 Feb 2024 11:47:12 +0100
Subject: [PATCH 04/16] windows iis userguide

---
 guides/windows_iis_setup.md | 107 ++++++++++++++++++++++++++++++++++++
 1 file changed, 107 insertions(+)
 create mode 100644 guides/windows_iis_setup.md

diff --git a/guides/windows_iis_setup.md b/guides/windows_iis_setup.md
new file mode 100644
index 0000000..be88612
--- /dev/null
+++ b/guides/windows_iis_setup.md
@@ -0,0 +1,107 @@
+# Setup Raven on Windows and IIS
+
+Work in progress
+
+## Requirements
+
+- Git
+- Application request routing installed and enabled
+  - https://www.iis.net/downloads/microsoft/application-request-routing
+  - https://bmscloud.no/vapcloud/help/engineeringhelp/en-us/23598993547.html
+
+## Setup
+
+### Clone repository:
+
+```powershell
+git clone https://git.nilu.no/raven/raven-administration
+```
+
+### Install and build client
+
+Inside the client folder run these commands.  
+This will create a `dist` folder
+
+```powershell
+npm install
+npm build
+```
+
+### Create a web.config file in the `dist` folder
+
+Change the api port if needed
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <system.webServer>
+    <rewrite>
+      <rules>
+        <rule name="backend" enabled="true" stopProcessing="true">
+          <match url="^api/(.*)$" />
+          <action type="Rewrite" url="http://localhost:5000/api/{R:1}"/>
+        </rule>
+        <rule name="frontend" stopProcessing="true">
+          <match url="^(?!api/)(.*)" />
+          <conditions logicalGrouping="MatchAll">
+            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
+            <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
+          </conditions>
+          <action type="Rewrite" url="/" />
+        </rule>
+      </rules>
+    </rewrite>
+  </system.webServer>
+</configuration>
+```
+
+### Install and build api
+
+Inside the `api`folder, create a virtual environment, activate it and istall packages
+
+```powershell
+python -m venv venv
+\venv\Scripts\activate
+pip install -r requirements.txt
+```
+
+### Create a web.config file
+
+```xml
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <system.webServer>
+        <handlers>
+            <add name="raven-administration-handler" path="*" verb="*" modules="FastCgiModule" scriptProcessor="<path_to_raven>\api\venv\Scripts\python.exe|<path_to_raven>\api\venv\Lib\site-packages\wfastcgi.py" resourceType="Unspecified" />
+        </handlers>
+        <urlCompression doStaticCompression="true" doDynamicCompression="true" />
+    </system.webServer>
+<appSettings>
+<add key="WSGI_HANDLER" value="app.app" />
+</appSettings>
+</configuration>
+```
+
+### Enable wfastcgi
+
+```powershell
+pip install wfastcgi
+wfastcgi-enable
+```
+
+### Add .env file
+
+```
+DB_URI = postgresql://postgres:password@host:5432/database
+JWT_ACCESS_TOKEN_EXPIRES_SECONDS = 3600
+JWT_SECRET_KEY = make-up-a-secure-key
+```
+
+### IIS
+
+Setup a new website on port 80 (or use the default website)  
+Set the path to the `dist` folder
+
+Setup a new website on port 5000  
+Set the path to the `api` folder  
+Set The applicationpool to `No managed code`
-- 
GitLab


From 05dc2e377a2b84df1003ff4e7b719f55e8c593d3 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 1 Feb 2024 11:48:42 +0100
Subject: [PATCH 05/16] spelling

---
 guides/windows_iis_setup.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/guides/windows_iis_setup.md b/guides/windows_iis_setup.md
index be88612..da700a3 100644
--- a/guides/windows_iis_setup.md
+++ b/guides/windows_iis_setup.md
@@ -57,7 +57,7 @@ Change the api port if needed
 
 ### Install and build api
 
-Inside the `api`folder, create a virtual environment, activate it and istall packages
+Inside the `api`folder, create a virtual environment, activate it and install packages
 
 ```powershell
 python -m venv venv
-- 
GitLab


From 489495683f456fa816151e0558f4dfbcda293954 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Wed, 7 Feb 2024 09:56:24 +0100
Subject: [PATCH 06/16] issue #33: set -9900 to null

---
 api/endpoints/qualitycontrol/validate/routes.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/endpoints/qualitycontrol/validate/routes.py b/api/endpoints/qualitycontrol/validate/routes.py
index 6a58777..a40ec34 100644
--- a/api/endpoints/qualitycontrol/validate/routes.py
+++ b/api/endpoints/qualitycontrol/validate/routes.py
@@ -26,7 +26,7 @@ def timevalues():
               o.sampling_point_id as "sampling_point_id",
               o.validation_flag,
               o.verification_flag,
-              o.value::double PRECISION
+              case when o.value = -9900 then null else o.value::double PRECISION end as "value"
             FROM observations o
             WHERE 1=1
             AND o.from_time >= %(from_dt)s
-- 
GitLab


From 0e7ee650f2fb1bcd59fccb0bee34453f506284b7 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Wed, 7 Feb 2024 10:17:56 +0100
Subject: [PATCH 07/16] bug: override width style on login container

---
 client/src/views/login/Login.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/src/views/login/Login.vue b/client/src/views/login/Login.vue
index 7e0ff4f..cdcc67c 100644
--- a/client/src/views/login/Login.vue
+++ b/client/src/views/login/Login.vue
@@ -53,7 +53,7 @@ const reset = () => {
 
 <template>
   <div class="flex justify-center mt-10 w-full">
-    <container class="m-auto w-80 !gap-0" v-if="!canCreateAdmin">
+    <container class="m-auto !w-80 !gap-0" v-if="!canCreateAdmin">
       <div class="p-2 flex flex-col">
         <div class="mb-1">Username:</div>
         <input type="text" placeholder="Username" class="n-input w-full" v-model="username" @keyup.enter="login" />
-- 
GitLab


From fd73a1243f7a36f92601a3185e56dd3eadb8da19 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Wed, 7 Feb 2024 11:20:10 +0100
Subject: [PATCH 08/16] bug: progressbar positionion issues

---
 client/src/components/ProgressBar.vue | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/client/src/components/ProgressBar.vue b/client/src/components/ProgressBar.vue
index 8bf157f..fd908d0 100644
--- a/client/src/components/ProgressBar.vue
+++ b/client/src/components/ProgressBar.vue
@@ -1,5 +1,5 @@
 <template>
-  <div class="transition-position duration-500 ease-in-out absolute top-0 left-0 right-0 h-1 z-[999]" :class="cls"></div>
+  <div class="transition-position duration-500 ease-in-out absolute left-0 right-0 h-1 z-[999]" :class="cls"></div>
 </template>
 
 <script setup>
@@ -24,7 +24,7 @@ onMounted(async () => {
 });
 
 const cls = computed(() => {
-  var s = show.value ? "top-0" : "-top-10";
+  var s = show.value ? "!top-0" : "!-top-10";
   s = s + (fail.value ? " bg-nord11" : " bg-nord7 animate-pulse");
   return s;
 });
-- 
GitLab


From 26faaed7afda9ca18bd2f48e34c2dd24978bec97 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Wed, 7 Feb 2024 15:34:53 +0100
Subject: [PATCH 09/16] feat: allow env in api or root folder

---
 api/config.py | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/api/config.py b/api/config.py
index 01be3e4..7b39b6a 100644
--- a/api/config.py
+++ b/api/config.py
@@ -4,7 +4,13 @@ from datetime import datetime
 from datetime import timedelta
 
 basedir = os.path.abspath(os.path.dirname(__file__))
-load_dotenv(os.path.join(basedir, '../.env'))
+
+env_in_api = os.path.isfile(os.path.join(basedir, '.env'))
+env_in_root = os.path.isfile(os.path.join(basedir, '../.env'))
+path = os.path.join(basedir, '.env') if env_in_api else os.path.join(basedir, '../.env')
+
+
+load_dotenv(path)
 
 
 class Config(object):
-- 
GitLab


From 0fddb223c09426eae917e34a2421fe2dd1d4ac8b Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Wed, 7 Feb 2024 15:35:06 +0100
Subject: [PATCH 10/16] bug: scaling bugs

---
 api/core/data/processing/scaling.py         |  1 +
 client/src/views/processing/scale/LAdd.vue  | 16 +++++++++-------
 client/src/views/processing/scale/Scale.vue |  1 +
 3 files changed, 11 insertions(+), 7 deletions(-)

diff --git a/api/core/data/processing/scaling.py b/api/core/data/processing/scaling.py
index 7051f62..2c1c62a 100644
--- a/api/core/data/processing/scaling.py
+++ b/api/core/data/processing/scaling.py
@@ -98,6 +98,7 @@ class Scaling:
               o.validation_flag, 
               o.import_value::DOUBLE PRECISION, 
               o.import_value::DOUBLE PRECISION as value, 
+              o.scaled_value::DOUBLE PRECISION,
               o.from_time, o.to_time, 
               extract(epoch from o.to_time)*1000 as to_epoch, 
               extract(epoch from o.from_time)*1000 as from_epoch
diff --git a/client/src/views/processing/scale/LAdd.vue b/client/src/views/processing/scale/LAdd.vue
index 210a2a3..d6f3e9a 100644
--- a/client/src/views/processing/scale/LAdd.vue
+++ b/client/src/views/processing/scale/LAdd.vue
@@ -4,32 +4,34 @@ const props = defineProps({
   obj: Object
 });
 
-// const obj = ref({});
+const _obj = ref({});
 
 watch(
   () => props.show,
-  () => (props.obj = {})
+  () => {
+    _obj.value = Object.assign({}, props.obj);
+  }
 );
 </script>
 
 <template>
-  <side-bar-crud :show="show" @cancel="$emit('close')" @commit="$emit('save', Object.assign({}, obj))">
+  <side-bar-crud :show="show" @cancel="$emit('close')" @commit="$emit('save', Object.assign({}, _obj))">
     <div class="mb-4 font-bold text-base border-b">Required</div>
     <div class="mb-2">
       <div class="font-bold">Zero point:</div>
-      <input type="number" class="n-input w-72" v-model="obj.zero_point" placeholder="float: Zero point value" />
+      <input type="number" class="n-input w-72" v-model="_obj.zero_point" placeholder="float: Zero point value" />
     </div>
     <div class="mb-2">
       <div class="font-bold">Span value:</div>
-      <input type="number" class="n-input w-72" v-model="obj.span_value" placeholder="float: Span value" />
+      <input type="number" class="n-input w-72" v-model="_obj.span_value" placeholder="float: Span value" />
     </div>
     <div class="mb-2">
       <div class="font-bold">Gas concentration:</div>
-      <input type="number" class="n-input w-72" v-model="obj.gas_concentration" placeholder="float: Gas concentration" />
+      <input type="number" class="n-input w-72" v-model="_obj.gas_concentration" placeholder="float: Gas concentration" />
     </div>
     <div class="mb-2">
       <div class="font-bold">Timestamp:</div>
-      <n-datetime v-model="obj.timestamp" class="!w-72" />
+      <n-datetime v-model="_obj.timestamp" class="!w-72" />
     </div>
   </side-bar-crud>
 </template>
diff --git a/client/src/views/processing/scale/Scale.vue b/client/src/views/processing/scale/Scale.vue
index 6c6c2d1..2676c94 100644
--- a/client/src/views/processing/scale/Scale.vue
+++ b/client/src/views/processing/scale/Scale.vue
@@ -87,6 +87,7 @@ const onShowAdd = () => {
 
 const onSaveAdd = async (o) => {
   Eventy.showMessage("Inserting scaling point. Please wait", "loading");
+  console.log("onSaveAdd", o);
   await Service.insert(o);
   await onShowScalingpoints();
   Eventy.showHideMessage("Scaling point added", "success", 5000);
-- 
GitLab


From ae57dbd31bb61786c44ae3215d6b8cc1125d6e65 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 8 Feb 2024 09:03:29 +0100
Subject: [PATCH 11/16] bug: set scaled value as null when rescaling

---
 api/core/data/processing/scaling.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/core/data/processing/scaling.py b/api/core/data/processing/scaling.py
index 2c1c62a..38ee5c4 100644
--- a/api/core/data/processing/scaling.py
+++ b/api/core/data/processing/scaling.py
@@ -98,7 +98,7 @@ class Scaling:
               o.validation_flag, 
               o.import_value::DOUBLE PRECISION, 
               o.import_value::DOUBLE PRECISION as value, 
-              o.scaled_value::DOUBLE PRECISION,
+              null as scaled_value,
               o.from_time, o.to_time, 
               extract(epoch from o.to_time)*1000 as to_epoch, 
               extract(epoch from o.from_time)*1000 as from_epoch
-- 
GitLab


From b0b19ddc40948f42a2354fadc822cb5e90d8bc3a Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 8 Feb 2024 09:23:23 +0100
Subject: [PATCH 12/16] bug: do not scale -9900 value

---
 api/core/data/processing/scaling.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/api/core/data/processing/scaling.py b/api/core/data/processing/scaling.py
index 38ee5c4..ee660c9 100644
--- a/api/core/data/processing/scaling.py
+++ b/api/core/data/processing/scaling.py
@@ -188,4 +188,6 @@ class Scaling:
 
     @staticmethod
     def __scalevalue__(zero, span, gas, value):
+        if value == -9900:
+            return value
         return (float(gas) / (float(span) - float(zero))) * (float(value) - float(zero))
-- 
GitLab


From b9be90b791ca6d9bc1db8a05e6bd2069a70994c7 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 8 Feb 2024 09:49:48 +0100
Subject: [PATCH 13/16] add: toggle between valid only values

---
 .../qualitycontrol/validate/routes.py         |  3 ++-
 .../qualitycontrol/validate/Validate.vue      | 26 ++++++++++++++++---
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/api/endpoints/qualitycontrol/validate/routes.py b/api/endpoints/qualitycontrol/validate/routes.py
index a40ec34..37cbd9b 100644
--- a/api/endpoints/qualitycontrol/validate/routes.py
+++ b/api/endpoints/qualitycontrol/validate/routes.py
@@ -26,7 +26,8 @@ def timevalues():
               o.sampling_point_id as "sampling_point_id",
               o.validation_flag,
               o.verification_flag,
-              case when o.value = -9900 then null else o.value::double PRECISION end as "value"
+              case when o.value = -9900 then null else o.value::double PRECISION end as "value",
+              case when o.validation_flag not in (1,2,3) then null else o.value::double PRECISION end as "valid_value_only"
             FROM observations o
             WHERE 1=1
             AND o.from_time >= %(from_dt)s
diff --git a/client/src/views/qualitycontrol/validate/Validate.vue b/client/src/views/qualitycontrol/validate/Validate.vue
index 842b6f8..c0c7e71 100644
--- a/client/src/views/qualitycontrol/validate/Validate.vue
+++ b/client/src/views/qualitycontrol/validate/Validate.vue
@@ -16,6 +16,7 @@ import Plot from "./plot";
 
 import IconLink from "~icons/ph/link-simple-duotone";
 import Container from "../../../components/Container.vue";
+import { watch } from "vue";
 
 const timeseries = ref([]);
 
@@ -27,6 +28,7 @@ const timevalues = ref([]);
 const ev = ref({});
 const showContextmenu = ref(false);
 const selectedRows = ref([]);
+const showValidOnly = ref(false);
 
 const showPlotAndTable = ref(false);
 
@@ -44,6 +46,13 @@ onMounted(async () => {
   if (route.query.ids || route.query.from || route.query.to) showData();
 });
 
+watch(
+  () => showValidOnly.value,
+  () => {
+    formatAndLoad();
+  }
+);
+
 const cmp_timeseries = computed(() => {
   return timeseries.value.filter((t) => {
     if (!t.fromtime && !t.totime) return true;
@@ -72,6 +81,10 @@ const load = async () => {
   if (!chart) {
     chart = new Chart("chart", Plot.config(onDatapointSelection));
   }
+  formatAndLoad();
+};
+const formatAndLoad = () => {
+  console.log("formatAndLoad");
   chart.data = formatValues();
   chart.update();
 };
@@ -127,11 +140,12 @@ const formatValues = () => {
   let colors = [];
   let data = [];
   timevalues.value.forEach((o) => {
-    var v = o.value == -9900 ? null : o.value;
+    var value_to_use = showValidOnly.value ? o.valid_value_only : o.value;
+    var v = value_to_use == -9900 ? null : value_to_use;
     var c = o.validation_flag < 1 ? "#BF616A" : "#A3BE8C";
     const n = Object.assign({}, o);
     colors.push(c);
-    data.push({ x: o.totime.replace(" ", "T"), y: o.value, obj: n });
+    data.push({ x: o.totime.replace(" ", "T"), y: v, obj: n });
   });
   return { datasets: [Plot.dataset("Value", data, colors)] };
 };
@@ -201,7 +215,13 @@ const onDatapointSelection = (event, sel, chart) => {
     </container>
 
     <div v-show="showPlotAndTable">
-      <container class="mt-4 !p-4 h-72"><canvas id="chart"></canvas></container>
+      <container class="mt-4 !p-4 h-72">
+        <div class="px-2 flex w-fit gap-2">
+          <div class="font-bold self-center flex-1 cursor-pointer" @click="showValidOnly = !showValidOnly">Show only valid values</div>
+          <n-checkbox v-model="showValidOnly" class="self-center" />
+        </div>
+        <canvas id="chart"></canvas>
+      </container>
 
       <div class="mt-4">
         <table id="validationId" class="n-table">
-- 
GitLab


From c028c0d2b3a3c5913a59cc99b4fc8c0e22bb517e Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 8 Feb 2024 10:00:54 +0100
Subject: [PATCH 14/16] new version

---
 api/endpoints/version/routes.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/api/endpoints/version/routes.py b/api/endpoints/version/routes.py
index c8ad9d9..6a7ccae 100644
--- a/api/endpoints/version/routes.py
+++ b/api/endpoints/version/routes.py
@@ -3,7 +3,7 @@ from flask_jwt_extended import create_access_token
 import requests
 
 version_endpoint = Blueprint('version', __name__)
-current_version = "3.0.17"
+current_version = "3.0.18"
 
 
 @version_endpoint.route('/api/version', methods=['GET'])
-- 
GitLab


From 0b7c744bc36dfd17fd956d06a84fcecaadc09dd3 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 8 Feb 2024 10:43:59 +0100
Subject: [PATCH 15/16] show imported values

---
 api/endpoints/qualitycontrol/validate/routes.py       | 5 +++--
 client/src/views/qualitycontrol/validate/Validate.vue | 6 ++++--
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/api/endpoints/qualitycontrol/validate/routes.py b/api/endpoints/qualitycontrol/validate/routes.py
index 37cbd9b..2e8a4fb 100644
--- a/api/endpoints/qualitycontrol/validate/routes.py
+++ b/api/endpoints/qualitycontrol/validate/routes.py
@@ -26,8 +26,9 @@ def timevalues():
               o.sampling_point_id as "sampling_point_id",
               o.validation_flag,
               o.verification_flag,
-              case when o.value = -9900 then null else o.value::double PRECISION end as "value",
-              case when o.validation_flag not in (1,2,3) then null else o.value::double PRECISION end as "valid_value_only"
+              o.value::double PRECISION ,
+              case when o.validation_flag not in (1,2,3) then null else o.value::double PRECISION end as "valid_value_only",
+              o.import_value::double PRECISION
             FROM observations o
             WHERE 1=1
             AND o.from_time >= %(from_dt)s
diff --git a/client/src/views/qualitycontrol/validate/Validate.vue b/client/src/views/qualitycontrol/validate/Validate.vue
index c0c7e71..4a5a4c4 100644
--- a/client/src/views/qualitycontrol/validate/Validate.vue
+++ b/client/src/views/qualitycontrol/validate/Validate.vue
@@ -215,12 +215,12 @@ const onDatapointSelection = (event, sel, chart) => {
     </container>
 
     <div v-show="showPlotAndTable">
-      <container class="mt-4 !p-4 h-72">
+      <container class="mt-4 !p-4 h-80">
         <div class="px-2 flex w-fit gap-2">
           <div class="font-bold self-center flex-1 cursor-pointer" @click="showValidOnly = !showValidOnly">Show only valid values</div>
           <n-checkbox v-model="showValidOnly" class="self-center" />
         </div>
-        <canvas id="chart"></canvas>
+        <canvas id="chart" class="!h-64"></canvas>
       </container>
 
       <div class="mt-4">
@@ -229,6 +229,7 @@ const onDatapointSelection = (event, sel, chart) => {
             <th>From</th>
             <th>To</th>
             <th>Value</th>
+            <th>Import value</th>
             <th>Validation</th>
             <th>Verification</th>
           </tr>
@@ -236,6 +237,7 @@ const onDatapointSelection = (event, sel, chart) => {
             <td>{{ row.fromtime }}</td>
             <td>{{ row.totime }}</td>
             <td>{{ row.value }}</td>
+            <td>{{ row.import_value }}</td>
             <td>{{ row.validation_flag }}</td>
             <td class="flex gap-1">
               <div class="self-center">{{ row.verification_flag }}</div>
-- 
GitLab


From e54cc051cef12b597ec094453aa57e36e77d3b23 Mon Sep 17 00:00:00 2001
From: cst <cst@nilu.no>
Date: Thu, 8 Feb 2024 10:50:44 +0100
Subject: [PATCH 16/16] removed console.logs

---
 client/src/views/processing/scale/Scale.vue           | 1 -
 client/src/views/qualitycontrol/validate/Validate.vue | 1 -
 2 files changed, 2 deletions(-)

diff --git a/client/src/views/processing/scale/Scale.vue b/client/src/views/processing/scale/Scale.vue
index 2676c94..6c6c2d1 100644
--- a/client/src/views/processing/scale/Scale.vue
+++ b/client/src/views/processing/scale/Scale.vue
@@ -87,7 +87,6 @@ const onShowAdd = () => {
 
 const onSaveAdd = async (o) => {
   Eventy.showMessage("Inserting scaling point. Please wait", "loading");
-  console.log("onSaveAdd", o);
   await Service.insert(o);
   await onShowScalingpoints();
   Eventy.showHideMessage("Scaling point added", "success", 5000);
diff --git a/client/src/views/qualitycontrol/validate/Validate.vue b/client/src/views/qualitycontrol/validate/Validate.vue
index 4a5a4c4..f6828e1 100644
--- a/client/src/views/qualitycontrol/validate/Validate.vue
+++ b/client/src/views/qualitycontrol/validate/Validate.vue
@@ -84,7 +84,6 @@ const load = async () => {
   formatAndLoad();
 };
 const formatAndLoad = () => {
-  console.log("formatAndLoad");
   chart.data = formatValues();
   chart.update();
 };
-- 
GitLab