Compare commits

...

23 Commits
0.9.0 ... main

Author SHA1 Message Date
95908c9290 doc: Add issues: grouped measures and measure visibility. 2021-07-19 08:03:12 -05:00
3c9c24f30b api: Consolidate AWS Secret usage to one secret per environment. 2021-07-05 15:19:04 -05:00
16c7852972 Update package version to 0.11.0 2021-07-05 11:41:43 -05:00
0c7ab9524d api: Ignore DEV and PROD config files (may contain sensitive info). 2021-07-05 11:41:06 -05:00
7fb26bab97 operations: Expose the update version script in the main Makefile. 2021-07-05 11:39:50 -05:00
ca70773a8c api: Update fiber_orm dependency to fix a bug in parsing PostgreSQL timestamps. 2021-07-05 11:39:23 -05:00
a0f9670688 api: Use the standard config pattern for KNOWN_ORIGINS. 2021-07-05 11:35:35 -05:00
2fd45ac35c api: Refresh the API CLI wrapper script. 2021-07-05 11:35:03 -05:00
3844e97c48 api: Updates to Makefile and configuration files.
- Re-organized and documented make targets.
- Parameterized the make targets to more cleanly support multiple
  development scenarios.
- Documented the different development scenarios in a README.
- Standardized the host port used when running locally.
- Updated DEV and PROD database config files to match current practice.
- Renamed `personal_measure_api.config.prod.json` to
  `personal_measure_api.config.docker.json` to more accurately reflect
  that this is the config used when building the docker image
  (regardless of which env it ends up in).
2021-07-05 11:31:30 -05:00
3416d2b85b Update package version to 0.10.0 2021-07-05 02:01:16 -05:00
f29b1a0967 operations: Update primary Makefile to reflect new API ECS-based build and deploy process. 2021-07-05 02:00:49 -05:00
e3f214d0da api: Update API to support Options requests for CORS. 2021-07-05 01:59:46 -05:00
c987d66504 api: Update Dockerfile and Makefile to support building and pushing to ECR. 2021-07-05 01:59:16 -05:00
bc06fc54bb operations: Complete migration to AWS ECS. 2021-07-05 01:57:39 -05:00
99a4c1fc94 web: Update environment configurations for jdb-software. 2021-07-05 00:17:17 -05:00
87ce9cc4d4 operations: WIP continuing definition for ECS-based API deployment. 2021-07-03 03:36:41 -05:00
c2c4c8473d Update Makefile to disable obsolete API deployment flow. 2021-07-03 03:35:59 -05:00
bb89f519e0 operations: WIP moving API to run as an ECS task. 2021-07-03 01:30:51 -05:00
20e0a0b09e api: Clean up Dockerfile, rebase onto Nim 1.4.8. 2021-07-03 01:30:26 -05:00
1449e1ffdd api: Updates for Nim 1.4.x. 2021-07-03 01:26:56 -05:00
526419afb3 api: Change dependencies from jdb-labs.com -> jdb-software.com 2021-07-03 01:26:24 -05:00
327c64f45a web: Update dependencies. 2021-03-07 17:53:32 -06:00
06e3bb5ea3 Switch to servor instead of the build-in vue-cli-service server for development tools. 2020-07-06 18:37:13 -05:00
37 changed files with 709 additions and 360 deletions

2
.gitignore vendored
View File

@ -3,6 +3,8 @@ api/personal_measure_api
api/postgres.container.id api/postgres.container.id
api/src/main/nim/personal_measure_api api/src/main/nim/personal_measure_api
api/src/main/nim/personal_measure_apipkg/db api/src/main/nim/personal_measure_apipkg/db
api/personal_measure_api.config.dev.json
api/personal_measure_api.config.prod.json
.DS_Store .DS_Store
node_modules node_modules

View File

@ -5,14 +5,12 @@ build: dist/personal-measure-api.tar.gz dist/personal-measure-web.tar.gz
clean: clean:
-rm -r dist -rm -r dist
-rm api/personal_measure_api
-rm -r web/dist -rm -r web/dist
-docker container prune
-docker image prune
dist/personal-measure-api.tar.gz: update-version:
-mkdir dist operations/update-version.sh
make -C api personal_measure_api
tar czf dist/personal-measure-api-${VERSION}.tar.gz -C api personal_measure_api
cp dist/personal-measure-api-${VERSION}.tar.gz dist/personal-measure-api.tar.gz
dist/personal-measure-web.tar.gz: dist/personal-measure-web.tar.gz:
-mkdir dist -mkdir dist
@ -20,18 +18,14 @@ dist/personal-measure-web.tar.gz:
tar czf dist/personal-measure-web-${VERSION}.tar.gz -C web/dist . tar czf dist/personal-measure-web-${VERSION}.tar.gz -C web/dist .
cp dist/personal-measure-web-${VERSION}.tar.gz dist/personal-measure-web.tar.gz cp dist/personal-measure-web-${VERSION}.tar.gz dist/personal-measure-web.tar.gz
deploy-api: dist/personal-measure-api.tar.gz deploy-api:
mkdir -p temp-deploy/personal-measure-api-${VERSION} make -C api personal_measure_api-image push-image
tar xzf dist/personal-measure-api-${VERSION}.tar.gz -C temp-deploy/personal-measure-api-${VERSION} cd operations/terraform && terraform apply -target module.${TARGET_ENV}_env.aws_ecs_task_definition.pmapi -target module.${TARGET_ENV}_env.aws_ecs_service.pmapi
-ssh pmapi@pmapi.jdb-labs.com "sudo systemctl stop personal_measure_api.$(TARGET_ENV).service"
scp temp-deploy/personal-measure-api-${VERSION}/personal_measure_api pmapi@pmapi.jdb-labs.com:/home/pmapi/$(TARGET_ENV)/personal_measure_api
ssh pmapi@pmapi.jdb-labs.com "sudo systemctl start personal_measure_api.$(TARGET_ENV).service"
rm -r temp-deploy
deploy-web: dist/personal-measure-web.tar.gz deploy-web: dist/personal-measure-web.tar.gz
mkdir -p temp-deploy/personal-measure-web-${VERSION} mkdir -p temp-deploy/personal-measure-web-${VERSION}
tar xzf dist/personal-measure-web-${VERSION}.tar.gz -C temp-deploy/personal-measure-web-${VERSION} tar xzf dist/personal-measure-web-${VERSION}.tar.gz -C temp-deploy/personal-measure-web-${VERSION}
aws s3 sync temp-deploy/personal-measure-web-${VERSION} s3://pm.jdb-labs.com/$(TARGET_ENV)/webroot aws s3 sync temp-deploy/personal-measure-web-${VERSION} s3://pm.jdb-software.com/$(TARGET_ENV)/webroot
TARGET_ENV=${TARGET_ENV} operations/invalidate-cdn-cache.sh TARGET_ENV=${TARGET_ENV} operations/invalidate-cdn-cache.sh
rm -r temp-deploy rm -r temp-deploy

View File

@ -1,26 +1,22 @@
FROM 063932952339.dkr.ecr.us-west-2.amazonaws.com/nim-alpine AS build FROM 063932952339.dkr.ecr.us-west-2.amazonaws.com/alpine-nim:nim-1.4.8 AS build
MAINTAINER jonathan@jdbernard.com MAINTAINER jonathan@jdbernard.com
# TODO: install db_migrate so we can use it below
# RUN nimble install https://git.jdb-labs.com/jdb/db-migrate.git
#RUN apt-get install -y libssl-dev
COPY personal_measure_api.nimble /pm-api/ COPY personal_measure_api.nimble /pm-api/
COPY src /pm-api/src COPY src /pm-api/src
WORKDIR /pm-api WORKDIR /pm-api
RUN nimble build -y RUN nimble build -y
FROM alpine FROM alpine
#RUN apt-get install -y postgresql-client EXPOSE 80
RUN apk -v --update add --no-cache \ RUN apk -v --update add --no-cache \
ca-certificates \ ca-certificates \
libressl2.7-libssl \ libcrypto1.1 \
libressl2.7-libcrypto \ libssl1.1 \
pcre \ pcre \
postgresql-client postgresql-client
COPY --from=build /pm-api/personal_measure_api / COPY --from=build /pm-api/personal_measure_api /
COPY personal_measure_api.config.prod.json /personal_measure_api.config.json COPY personal_measure_api.config.docker.json /personal_measure_api.config.json
CMD ["/personal_measure_api", "serve"] CMD ["/personal_measure_api", "serve"]
# TODO: replace the above with something like: # TODO: replace the above with something like:

View File

@ -1,31 +1,123 @@
PGSQL_CONTAINER_ID=`cat postgres.container.id` PGSQL_CONTAINER_ID=`cat postgres.container.id`
DB_NAME="personal_measure"
SOURCES=$(wildcard src/main/nim/*.nim) $(wildcard src/main/nim/personal_measure_apipkg/*.nim) SOURCES=$(wildcard src/main/nim/*.nim) $(wildcard src/main/nim/personal_measure_apipkg/*.nim)
serve: personal_measure_api start-postgres # Variables that can be overriden
# -------------------------------
# AWS Account URL for the ECR repository
ECR_ACCOUNT_URL ?= 063932952339.dkr.ecr.us-west-2.amazonaws.com
# The version number that will be tagged the container image. You might want to
# override this when doing local development to create local versions that are
# reflect changes not yet committed.
VERSION ?= `git describe`
# The port on the host machine (not the container)
PORT ?= 8100
# The name of the database (used then creating a local Postgres container)
DB_NAME ?= personal_measure
# The database connection string. You would change this to point the API at a
# different database server (default is the local Postgres container).
DB_CONN_STRING ?= host=localhost dbname=$(DB_NAME) user=postgres password=password port=5500
# The API authentication secret (used for hashing passwords, etc.)
AUTH_SECRET ?= 123abc
default: start-postgres serve-docker
# Building and deploying the API container image
# ----------------------------------------------
personal_measure_api-image: $(SOURCES)
# Build the container image.
docker image build -t $(ECR_ACCOUNT_URL)/personal_measure_api:$(VERSION) .
push-image: personal_measure_api-image
# Push the container image to the private AWS ECR
docker push $(ECR_ACCOUNT_URL)/personal_measure_api:$(VERSION)
# Running the API locally on bare metal
# -------------------------------------
personal_measure_api: $(SOURCES)
# Build the API
nimble build
serve: personal_measure_api
# Run the API on this machine. Note that configuration is taken by default
# from the `personal_measure_api.config.json` file, but environment variables
# specified when running make can be used to override these (to change the
# DB_CONN_STRING, for example).
./personal_measure_api serve ./personal_measure_api serve
# Running the API locally in a container
# --------------------------------------
serve-docker: personal_measure_api-image
# Run the API in a docker container. Note that the configuration loaded into
# the Docker container defines very little of the actual configuration as
# environment variables are used in the deployed environments. Accordingly,
# we must specify them explicitly here.
docker run \
-e AUTH_SECRET=$(AUTH_SECRET) \
-e PORT=80 \
-e "DB_CONN_STRING=$(DB_CONN_STRING)" \
-e 'KNOWN_ORIGINS=["https://curl.localhost"]' \
-p 127.0.0.1:$(PORT):80/tcp \
$(ECR_ACCOUNT_URL)/personal_measure_api:$(VERSION)
# Managing Postgres in a local container
# --------------------------------------
#
# This supports local development on this machine. These commands rely on a
# file named `postgres.container.id` to track the existing and ID of the
# local Postgres instance.
postgres.container.id: postgres.container.id:
# This creates a new local Postegres container and initializes the PM API
# database scheme.
docker run --name postgres-$(DB_NAME) -e POSTGRES_PASSWORD=password -p 5500:5432 -d postgres > postgres.container.id docker run --name postgres-$(DB_NAME) -e POSTGRES_PASSWORD=password -p 5500:5432 -d postgres > postgres.container.id
sleep 5 sleep 5
PGPASSWORD=password psql -p 5500 -U postgres -h localhost -c "CREATE DATABASE $(DB_NAME);" PGPASSWORD=password psql -p 5500 -U postgres -h localhost -c "CREATE DATABASE $(DB_NAME);"
db_migrate up -c database-local.json db_migrate up -c database-local.json
start-postgres: postgres.container.id start-postgres: postgres.container.id
# Start the existing local Postgres container
docker start $(PGSQL_CONTAINER_ID) docker start $(PGSQL_CONTAINER_ID)
sleep 1 sleep 1
db_migrate up -c database-local.json db_migrate up -c database-local.json
stop-postgres: postgres.container.id stop-postgres: postgres.container.id
# Stop the existing local Postgres container
docker stop $(PGSQL_CONTAINER_ID) docker stop $(PGSQL_CONTAINER_ID)
delete-postgres-container: delete-postgres-container:
# Delete the local Postgres container. Note that this will destroy any data
# in this database instance.
-docker stop $(PGSQL_CONTAINER_ID) -docker stop $(PGSQL_CONTAINER_ID)
docker container rm $(PGSQL_CONTAINER_ID) docker container rm $(PGSQL_CONTAINER_ID)
rm postgres.container.id rm postgres.container.id
connect: connect-postgres:
# Connect to the Postgres instance running in the local container
PGPASSWORD=password psql -p 5500 -U postgres -h localhost ${DB_NAME} PGPASSWORD=password psql -p 5500 -U postgres -h localhost ${DB_NAME}
personal_measure_api: $(SOURCES)
nimble build # Utility
# -------
ecr-auth:
# Authenticate docker to the AWS private elastic container repository.
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 063932952339.dkr.ecr.us-west-2.amazonaws.com
echo-vars:
@echo \
" ECR_ACCOUNT_URL=$(ECR_ACCOUNT_URL)\n" \
"VERSION=$(VERSION)\n" \
"PORT=$(PORT)\n" \
"DB_NAME=$(DB_NAME)\n" \
"DB_CONN_STRING=$(DB_CONN_STRING)\n" \
"AUTH_SECRET=$(AUTH_SECRET)\n"

30
api/README.md Normal file
View File

@ -0,0 +1,30 @@
## Local Development
Examples of different local development & testing scenarios:
- Bare-metal API server, local Postgres container
make start-postgres
make serve
- Bare-metal API server, different Postgres server
DB_CONN_STRING="host=<db-hostname> user=pmapi password=<pwd>" make serve
- Docker API Server, local Postgres container
make start-postgres
VERSION=0.X.0-alpha make serve-docker
- Docker API server, different Postgres server
DB_CONN_STRING="host=<db-hostname> user=pmapi password=<pwd>" \
VERSION=0.X.0-alpha \
make serve-docker
All of the available `make` targets are documented inline; see the
[Makefile](./Makefile) for more details.
### Using the API CLI wrapper
The API CLI wrapper

View File

@ -1,5 +1,5 @@
{ {
"driver": "postgres", "driver": "postgres",
"connectionString": "host=localhost port=5999 dbname=personal_measure_dev user=postgres", "connectionString": "host=localhost port=5432 dbname=personal_measure_dev user=pmapi",
"sqlDir": "src/main/sql/migrations" "sqlDir": "src/main/sql/migrations"
} }

View File

@ -1,5 +1,5 @@
{ {
"driver": "postgres", "driver": "postgres",
"connectionString": "host=localhost port=5999 dbname=personal_measure user=postgres", "connectionString": "host=localhost port=5432 dbname=personal_measure user=pmapi",
"sqlDir": "src/main/sql/migrations" "sqlDir": "src/main/sql/migrations"
} }

View File

@ -0,0 +1,4 @@
{
"debug":false,
"pwdCost":11
}

View File

@ -2,7 +2,7 @@
"authSecret":"bifekHuffIs3", "authSecret":"bifekHuffIs3",
"dbConnString":"host=localhost port=5500 dbname=personal_measure user=postgres password=password", "dbConnString":"host=localhost port=5500 dbname=personal_measure user=postgres password=password",
"debug":true, "debug":true,
"port":8081, "port":8100,
"pwdCost":11, "pwdCost":11,
"knownOrigins": [ "https://curl.localhost" ] "knownOrigins": [ "https://curl.localhost" ]
} }

View File

@ -1,6 +0,0 @@
{
"debug":false,
"port":80,
"pwdCost":11,
"knownOrigins": [ "https://pm.jdb-labs.com" ]
}

View File

@ -2,7 +2,7 @@
include "src/main/nim/personal_measure_apipkg/version.nim" include "src/main/nim/personal_measure_apipkg/version.nim"
version = "0.9.0" version = "0.11.0"
author = "Jonathan Bernard" author = "Jonathan Bernard"
description = "JDB\'s Personal Measures API" description = "JDB\'s Personal Measures API"
license = "MIT" license = "MIT"
@ -16,6 +16,6 @@ skipExt = @["nim"]
requires @["nim >= 0.19.4", "bcrypt", "docopt >= 0.6.8", "isaac >= 0.1.3", requires @["nim >= 0.19.4", "bcrypt", "docopt >= 0.6.8", "isaac >= 0.1.3",
"jester >= 0.4.3", "jwt", "tempfile", "uuids >= 0.1.10" ] "jester >= 0.4.3", "jwt", "tempfile", "uuids >= 0.1.10" ]
requires "https://git.jdb-labs.com/jdb/nim-cli-utils.git >= 0.6.3" requires "https://git.jdb-software.com/jdb/nim-cli-utils.git >= 0.6.3"
requires "https://git.jdb-labs.com/jdb/nim-time-utils.git >= 0.5.2" requires "https://git.jdb-software.com/jdb/nim-time-utils.git >= 0.5.2"
requires "https://git.jdb-labs.com/jdb-labs/fiber-orm-nim.git >= 0.3.0" requires "https://git.jdb-software.com/jdb-software/fiber-orm-nim.git >= 0.3.2"

View File

@ -27,26 +27,22 @@ proc loadConfig*(args: Table[string, docopt.Value] = initTable[string, docopt.Va
try: json = parseFile(filePath) try: json = parseFile(filePath)
except: except:
json = %DEFAULT_CONFIG json = %DEFAULT_CONFIG
if not existsFile(filePath): if not fileExists(filePath):
info "created new configuration file \"" & filePath & "\"" info "created new configuration file \"" & filePath & "\""
filePath.writeFile($json) filePath.writeFile($json)
else: else:
warn "Cannot read configuration file \"" & filePath & "\":\n\t" & warn "Cannot read configuration file \"" & filePath & "\":\n\t" &
getCurrentExceptionMsg() getCurrentExceptionMsg()
let knownOriginsArray =
if json.hasKey("knownOrigins"): json["knownOrigins"]
else: newJArray()
let cfg = CombinedConfig(docopt: args, json: json) let cfg = CombinedConfig(docopt: args, json: json)
result = PMApiConfig( result = PMApiConfig(
authSecret: cfg.getVal("auth-secret"), authSecret: cfg.getVal("auth-secret"),
dbConnString: cfg.getVal("db-conn-string"), dbConnString: cfg.getVal("db-conn-string"),
debug: "true".startsWith(cfg.getVal("debug", "false").toLower()), debug: "true".startsWith(cfg.getVal("debug", "false").toLower()),
port: parseInt(cfg.getVal("port", "8080")), port: parseInt(cfg.getVal("port", "8100")),
pwdCost: cast[int8](parseInt(cfg.getVal("pwd-cost", "11"))), pwdCost: cast[int8](parseInt(cfg.getVal("pwd-cost", "11"))),
knownOrigins: toSeq(knownOriginsArray).mapIt(it.getStr)) knownOrigins: cfg.getVal("known-origins")[1..^2].split(',').mapIt(it[1..^2]))
proc initContext(args: Table[string, docopt.Value]): PMApiContext = proc initContext(args: Table[string, docopt.Value]): PMApiContext =
@ -114,6 +110,6 @@ Options:
if args["serve"]: start(ctx) if args["serve"]: start(ctx)
except: except:
fatal "pit: " & getCurrentExceptionMsg() fatal "personal_measure_api: " & getCurrentExceptionMsg()
#raise getCurrentException() #raise getCurrentException()
quit(QuitFailure) quit(QuitFailure)

View File

@ -1,5 +1,6 @@
import asyncdispatch, base64, jester, json, jwt, logging, options, sequtils, import asyncdispatch, base64, jester, json, jwt, logging, options, sequtils,
times, uuids times, uuids
from httpcore import HttpMethod
from unicode import capitalize from unicode import capitalize
import strutils except capitalize import strutils except capitalize
import timeutils import timeutils
@ -58,6 +59,29 @@ template jsonResp(code: HttpCode, body: string = "", headersToSend: RawHeaders =
body body
) )
template optionsResp(allowedMethods: seq[HttpMethod]) =
let reqOrigin =
if request.headers.hasKey("Origin"): $(request.headers["Origin"])
else: ""
let corsHeaders =
if ctx.cfg.knownOrigins.contains(reqOrigin):
@{
"Access-Control-Allow-Origin": reqOrigin,
"Access-Control-Allow-Credentials": "true",
"Access-Control-Allow-Methods": allowedMethods.mapIt($it).join(", "),
"Access-Control-Allow-Headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
}
else: @{:}
halt(
Http200,
corsHeaders,
""
)
template jsonResp(body: string) = jsonResp(Http200, body) template jsonResp(body: string) = jsonResp(Http200, body)
template statusResp(code: HttpCode, details: string = "", headersToSend: RawHeaders = @{:} ) = template statusResp(code: HttpCode, details: string = "", headersToSend: RawHeaders = @{:} ) =
@ -97,7 +121,7 @@ proc fromJWT*(ctx: PMApiContext, strTok: string): Session =
## Validate a given JWT and extract the session data. ## Validate a given JWT and extract the session data.
let jwt = toJWT(strTok) let jwt = toJWT(strTok)
var secret = ctx.cfg.authSecret var secret = ctx.cfg.authSecret
if not jwt.verify(secret): raiseEx "Unable to verify auth token." if not jwt.verify(secret, HS256): raiseEx "Unable to verify auth token."
jwt.verifyTimeClaims() jwt.verifyTimeClaims()
# Find the user record (if authenticated) # Find the user record (if authenticated)
@ -212,9 +236,13 @@ proc start*(ctx: PMApiContext): void =
routes: routes:
options "/version": optionsResp(@[HttpGet])
get "/version": get "/version":
jsonResp($(%("personal_measure_api v" & PM_API_VERSION))) jsonResp($(%("personal_measure_api v" & PM_API_VERSION)))
options "/auth-token": optionsResp(@[HttpPost])
post "/auth-token": post "/auth-token":
try: try:
@ -226,6 +254,8 @@ proc start*(ctx: PMApiContext): void =
except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg()) except JsonParsingError: statusResp(Http400, getCurrentExceptionMsg())
except: statusResp(Http401, getCurrentExceptionMsg()) except: statusResp(Http401, getCurrentExceptionMsg())
options "/change-pwd": optionsResp(@[HttpPost])
post "/change-pwd": post "/change-pwd":
checkAuth() checkAuth()
@ -247,6 +277,8 @@ proc start*(ctx: PMApiContext): void =
error "internal error changing password: " & getCurrentExceptionMsg() error "internal error changing password: " & getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/change-pwd/@userId": optionsResp(@[HttpPost])
post "/change-pwd/@userId": post "/change-pwd/@userId":
checkAuth(true) checkAuth(true)
@ -268,6 +300,8 @@ proc start*(ctx: PMApiContext): void =
error "internal error changing password: " & getCurrentExceptionMsg() error "internal error changing password: " & getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/user": optionsResp(@[HttpGet, HttpPut])
get "/user": get "/user":
checkAuth() checkAuth()
@ -292,6 +326,8 @@ proc start*(ctx: PMApiContext): void =
error "Could not update user information:\n\t" & getCurrentExceptionMsg() error "Could not update user information:\n\t" & getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/users": optionsResp(@[HttpGet, HttpPost])
get "/users": get "/users":
checkAuth(true) checkAuth(true)
@ -320,6 +356,8 @@ proc start*(ctx: PMApiContext): void =
error "Could not create new user:\n\t" & getCurrentExceptionMsg() error "Could not create new user:\n\t" & getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/users/@userId": optionsResp(@[HttpGet, HttpDelete])
get "/users/@userId": get "/users/@userId":
checkAuth(true) checkAuth(true)
@ -340,6 +378,8 @@ proc start*(ctx: PMApiContext): void =
except: statusResp(Http500, getCurrentExceptionMsg()) except: statusResp(Http500, getCurrentExceptionMsg())
options "/api-tokens": optionsResp(@[HttpGet, HttpPost])
get "/api-tokens": get "/api-tokens":
checkAuth() checkAuth()
@ -374,6 +414,8 @@ proc start*(ctx: PMApiContext): void =
debug getCurrentExceptionMsg() debug getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/api-tokens/@tokenId": optionsResp(@[HttpGet, HttpDelete])
get "/api-tokens/@tokenId": get "/api-tokens/@tokenId":
checkAuth() checkAuth()
@ -394,6 +436,8 @@ proc start*(ctx: PMApiContext): void =
# Measure # Measure
options "/measures": optionsResp(@[HttpGet, HttpPost])
get "/measures": get "/measures":
checkAuth() checkAuth()
@ -438,6 +482,8 @@ proc start*(ctx: PMApiContext): void =
error "unable to create new measure:\n\t" & getCurrentExceptionMsg() error "unable to create new measure:\n\t" & getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/measures/@slug": optionsResp(@[HttpGet, HttpPost, HttpDelete])
get "/measures/@slug": get "/measures/@slug":
checkAuth() checkAuth()
@ -491,6 +537,9 @@ proc start*(ctx: PMApiContext): void =
statusResp(Http500) statusResp(Http500)
# Measurements # Measurements
options "/measurements/@slug": optionsResp(@[HttpGet, HttpPost])
get "/measurements/@slug": get "/measurements/@slug":
checkAuth() checkAuth()
@ -528,6 +577,8 @@ proc start*(ctx: PMApiContext): void =
error "unable to add measurement:\n\t" & getCurrentExceptionMsg() error "unable to add measurement:\n\t" & getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/measurements/@slug/@id": optionsResp(@[HttpGet, HttpPut, HttpDelete])
get "/measurements/@slug/@id": get "/measurements/@slug/@id":
checkAuth() checkAuth()
@ -580,6 +631,8 @@ proc start*(ctx: PMApiContext): void =
error "unable to delete measurement:\n\t" & getCurrentExceptionMsg() error "unable to delete measurement:\n\t" & getCurrentExceptionMsg()
statusResp(Http500) statusResp(Http500)
options "/log": optionsResp(@[HttpPost])
post "/log": post "/log":
checkAuth() checkAuth()
@ -597,6 +650,8 @@ proc start*(ctx: PMApiContext): void =
except BadRequestError: statusResp(Http400, getCurrentExceptionMsg()) except BadRequestError: statusResp(Http400, getCurrentExceptionMsg())
except: statusResp(Http500, getCurrentExceptionMsg()) except: statusResp(Http500, getCurrentExceptionMsg())
options "/log/batch": optionsResp(@[HttpPost])
post "/log/batch": post "/log/batch":
checkAuth() checkAuth()

View File

@ -1,4 +1,4 @@
import db_postgres, fiber_orm, uuids import db_postgres, fiber_orm, sequtils, uuids
import ./models import ./models
@ -8,7 +8,7 @@ type
PMApiDb* = ref object PMApiDb* = ref object
conn: DbConn conn: DbConn
proc connect*(connString: string): PMApiDb = proc connect*(connString: string): PMApiDb =
result = PMApiDb(conn: open("", "", "", connString)) result = PMApiDb(conn: open("", "", "", connString))

View File

@ -1 +1 @@
const PM_API_VERSION* = "0.9.0" const PM_API_VERSION* = "0.11.0"

View File

@ -1,24 +1,37 @@
#!/bin/bash #!/bin/bash
api_base_url="${PM_API_BASE_URL:-http://localhost:8081}" api_base_url="${PM_API_BASE_URL:-http://localhost:8100/v0}"
if [ $# -eq 1 ]; then if [ $# -eq 1 ]; then
url="$1" url="$1"
method="GET" method="GET"
data="" data=""
elif [ $# -eq 2 ]; then elif [ $# -eq 2 ]; then
method="$1" if [ $1 == "auth-token" ]; then
url="$2" curl -s -X POST \
data="" -H "Origin: https://curl.localhost" \
else "${api_base_url}/auth-token" \
-d "$2" \
| xargs printf "Bearer %s" \
> credential
exit 0
else
method="$1"
url="$2"
data=""
fi
else
method="$1" method="$1"
url="$2" url="$2"
data="$3" data="$3"
fi fi
if [[ ! $url = /* ]]; then url="/$url"; fi
curl -s -X "$method" \ curl -s -X "$method" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "Authorization: $(cat credential)" \ -H "Authorization: $(cat credential)" \
-H "Origin: https://curl.localhost" \ -H "Origin: https://curl.localhost" \
"${api_base_url}/api/$url" \ "${api_base_url}$url" \
-d "$data" \ -d "$data" \
-v | jq .

View File

@ -1 +1 @@
007 010

View File

@ -0,0 +1 @@
### Support rolling averages in graph displays.

View File

@ -0,0 +1,3 @@
### Toggle Measure Visibility
Allow the user to choose whether a measure should be visible or hidden by default.

View File

@ -0,0 +1,3 @@
### Grouped Measures
Create a measure type that is just a grouping of several other measures. For example, it would be nice to be able to group all workout-related measures into one group. The graph could show an overlay of all the different measures on one graph.

View File

@ -1,10 +0,0 @@
<RoutingRules>
<RoutingRule>
<Condition>
<KeyPrefixEquals>api</KeyPrefixEquals>
</Condition>
<Redirect>
<HostName>https://pmapi.jdbernard.com</HostName>
</Redirect>
</RoutingRule>
</RoutingRules>

View File

@ -1,33 +0,0 @@
server {
listen 80;
server_name pmapi-dev.jdb-labs.com;
return 301 https://pmapi-dev.jdb-labs.com$request_uri;
}
server {
listen 443;
server_name pmapi-dev.jdb-labs.com;
ssl on;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://pm-dev.jdb-labs.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
return 204;
}
proxy_pass http://localhost:8281;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@ -1,33 +0,0 @@
server {
listen 80;
server_name pmapi.jdb-labs.com;
return 301 https://pmapi.jdb-labs.com$request_uri;
}
server {
listen 443;
server_name pmapi.jdb-labs.com;
ssl on;
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://pm.jdb-labs.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';
return 204;
}
proxy_pass http://localhost:8280;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

View File

@ -7,5 +7,5 @@ variable "aws_region" {
variable "app_root_url" { variable "app_root_url" {
description = "Name of the S3 bucket to store deployed artifacts, logs, etc." description = "Name of the S3 bucket to store deployed artifacts, logs, etc."
default = "pm.jdb-labs.com" default = "pm.jdb-software.com"
} }

View File

@ -6,18 +6,18 @@ data "aws_iam_policy_document" "bucket_access_policy" {
principals { principals {
type = "AWS" type = "AWS"
identifiers = [ "${aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn}" ] identifiers = [ aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn ]
} }
} }
statement { statement {
actions = [ "s3:ListBucket" ] actions = [ "s3:ListBucket" ]
effect = "Allow" effect = "Allow"
resources = [ "${var.artifact_bucket.arn}" ] resources = [ var.artifact_bucket.arn ]
principals { principals {
type = "AWS" type = "AWS"
identifiers = [ "${aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn}" ] identifiers = [ aws_cloudfront_origin_access_identity.origin_access_identity.iam_arn ]
} }
} }
} }
@ -26,22 +26,18 @@ output "oai_access_policy" {
value = data.aws_iam_policy_document.bucket_access_policy value = data.aws_iam_policy_document.bucket_access_policy
} }
locals {
env_domain_name = "pm${var.environment == "prod" ? "" : "-${var.environment}"}.jdb-labs.com"
}
resource "aws_cloudfront_origin_access_identity" "origin_access_identity" { resource "aws_cloudfront_origin_access_identity" "origin_access_identity" {
comment = "OAI for Personal Measure {$var.environment} environment." comment = "OAI for Personal Measure {$var.environment} environment."
} }
resource "aws_cloudfront_distribution" "s3_distribution" { resource "aws_cloudfront_distribution" "s3_distribution" {
origin { origin {
domain_name = "${var.artifact_bucket.bucket_regional_domain_name}" domain_name = var.artifact_bucket.bucket_regional_domain_name
origin_id = "S3-PersonalMeasure-${var.environment}" origin_id = "S3-PersonalMeasure-${var.environment}"
origin_path = "/${var.environment}/webroot" origin_path = "/${var.environment}/webroot"
s3_origin_config { s3_origin_config {
origin_access_identity = "${aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path}" origin_access_identity = aws_cloudfront_origin_access_identity.origin_access_identity.cloudfront_access_identity_path
} }
} }
@ -52,11 +48,11 @@ resource "aws_cloudfront_distribution" "s3_distribution" {
logging_config { logging_config {
include_cookies = false include_cookies = false
bucket = "${var.artifact_bucket.bucket_domain_name}" bucket = var.artifact_bucket.bucket_domain_name
prefix = "${var.environment}/logs/cloudfront" prefix = "${var.environment}/logs/cloudfront"
} }
aliases = ["${local.env_domain_name}"] aliases = [local.app_domain_name]
default_cache_behavior { default_cache_behavior {
allowed_methods = ["GET", "HEAD", "OPTIONS"] allowed_methods = ["GET", "HEAD", "OPTIONS"]
@ -92,11 +88,11 @@ resource "aws_cloudfront_distribution" "s3_distribution" {
} }
} }
tags = { tags = {
Environment = "${var.environment}" Environment = local.environment_name
} }
viewer_certificate { viewer_certificate {
acm_certificate_arn = "${var.cloudfront_ssl_certificate_arn}" acm_certificate_arn = data.terraform_remote_state.jdbsoft.outputs.aws_acm_certificate_jdbsoft_us_east_1.arn
ssl_support_method = "sni-only" ssl_support_method = "sni-only"
} }
} }

View File

@ -0,0 +1,25 @@
resource "aws_route53_record" "app_domain" {
zone_id = data.terraform_remote_state.jdbsoft.outputs.aws_route53_zone_jdbsoft.zone_id
name = local.app_domain_name
type = "A"
alias {
name = aws_cloudfront_distribution.s3_distribution.domain_name
zone_id = aws_cloudfront_distribution.s3_distribution.hosted_zone_id
evaluate_target_health = false
}
depends_on = [aws_cloudfront_distribution.s3_distribution ]
}
resource "aws_route53_record" "api_domain" {
zone_id = data.terraform_remote_state.jdbsoft.outputs.aws_route53_zone_jdbsoft.zone_id
name = local.api_domain_name
type = "A"
alias {
name = data.terraform_remote_state.jdbsoft.outputs.aws_lb_jdbsoft.dns_name
zone_id = data.terraform_remote_state.jdbsoft.outputs.aws_lb_jdbsoft.zone_id
evaluate_target_health = false
}
}

View File

@ -0,0 +1,75 @@
resource "aws_secretsmanager_secret" "pmapi" {
name = "${local.environment_name}-Config"
tags = { Environment = local.environment_name }
}
resource "aws_ecs_task_definition" "pmapi" {
family = local.environment_name
network_mode = "bridge"
requires_compatibilities = ["EC2"]
execution_role_arn = aws_iam_role.ecs_task.arn
# See https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html
container_definitions = jsonencode([
{
name = local.environment_name
image = "${var.ecr_repo.repository_url}:${data.external.git_describe.result.version}"
cpu = 128
memory = 128
memoryReservation = 32
environment = [
{
name = "PORT"
value = "80"
}
]
portMappings = [
{
protocol = "tcp"
containerPort = 80
}
]
secrets = [
{
name = "AUTH_SECRET"
description = "Auth secret used to hash and salt passwords."
valueFrom = "${aws_secretsmanager_secret.pmapi.arn}:authSecret::"
},
{
name = "DB_CONN_STRING"
description = "Connection string with user credentials."
valueFrom = "${aws_secretsmanager_secret.pmapi.arn}:dbConnString::"
},
{
name = "KNOWN_ORIGINS"
description = "Connection string with user credentials."
valueFrom = "${aws_secretsmanager_secret.pmapi.arn}:knownOrigins::"
}
]
}
])
tags = {
Name = local.api_domain_name
Environment = local.environment_name
}
}
resource "aws_ecs_service" "pmapi" {
name = local.environment_name
cluster = data.terraform_remote_state.jdbsoft.outputs.aws_ecs_cluster_ortis.id
task_definition = aws_ecs_task_definition.pmapi.arn
desired_count = 1
launch_type = "EC2"
load_balancer {
target_group_arn = aws_lb_target_group.pmapi.arn
container_name = local.environment_name
container_port = 80
}
tags = {
Name = local.api_domain_name
Environment = local.environment_name
}
}

View File

@ -0,0 +1,69 @@
resource "aws_iam_role" "ecs_task" {
name = "${local.environment_name}-EcsTaskRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}
]
})
inline_policy {
name = "AllowSecretsAccessForPmApiTasks"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"secretsmanager:GetSecretValue",
"kms:Decrypt"
]
Resource = [
aws_secretsmanager_secret.pmapi.arn
]
}
]
})
}
inline_policy {
name = "AllowAccessToEcrForPmApiTasks"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecr:GetAuthorizationToken"
]
Resource = [ "*" ]
},
{
Effect = "Allow"
Action = [
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:DescribeImages",
"ecr:GetDownloadUrlForLayer"
]
Resource = [
var.ecr_repo.arn
]
}
]
})
}
tags = {
Name = "PersonalMeasure-EcsTaskRole"
Environment = local.environment_name
}
}

View File

@ -0,0 +1,43 @@
resource "aws_lb_target_group" "pmapi" {
name = "${local.environment_name}-${substr(uuid(), 0, 2)}"
port = 80
protocol = "HTTP"
target_type = "instance"
vpc_id = data.terraform_remote_state.jdbsoft.outputs.aws_vpc_jdbsoft.id
health_check {
enabled = true
matcher = "200"
path = "/v0/version"
}
lifecycle {
create_before_destroy = true
ignore_changes = [name]
}
tags = {
Name = local.api_domain_name
Environment = local.environment_name
}
}
resource "aws_lb_listener_rule" "pmapi" {
listener_arn = data.terraform_remote_state.jdbsoft.outputs.aws_lb_listener_https.arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.pmapi.arn
}
condition {
host_header {
values = [ local.api_domain_name ]
}
}
tags = {
Name = "${local.api_domain_name} HTTPS"
Environment = local.environment_name
}
}

View File

@ -8,6 +8,27 @@ variable "artifact_bucket" {
description = "The aws_s3_bucket object representing the artifact bucket where deployed artifacts, logs, etc. live." description = "The aws_s3_bucket object representing the artifact bucket where deployed artifacts, logs, etc. live."
} }
variable "cloudfront_ssl_certificate_arn" { variable "ecr_repo" {
description = "ARN of the managed SSL certificate to use for this environment." description = "ECR repository information."
}
locals {
environment_name = "PersonalMeasure-${var.environment}"
app_domain_name = "pm${var.environment == "prod" ? "" : "-${var.environment}"}.jdb-software.com"
api_domain_name = "pmapi${var.environment == "prod" ? "" : "-${var.environment}"}.jdb-software.com"
}
data "external" "git_describe" {
program = ["sh", "-c", "git describe | xargs printf '{\"version\": \"%s\"}'"]
}
data "terraform_remote_state" "jdbsoft" {
backend = "s3"
config = {
bucket = "operations.jdb-software.com"
region = "us-west-2"
key = "terraform/operations.tfstate"
dynamodb_table = "terraform-state-lock.jdb-software.com"
}
} }

View File

@ -0,0 +1,8 @@
resource "aws_ecr_repository" "personal_measure_api" {
name = "personal_measure_api"
image_tag_mutability = "IMMUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}

View File

@ -3,40 +3,24 @@ provider "aws" {
} }
resource "aws_s3_bucket" "personal_measure" { resource "aws_s3_bucket" "personal_measure" {
bucket = "${var.app_root_url}" bucket = var.app_root_url
acl = "log-delivery-write" acl = "log-delivery-write"
} }
resource "aws_dynamodb_table" "dynamodb_terraform-state-lock" {
name = "terraform-state-lock.${var.app_root_url}"
hash_key = "LockID"
read_capacity = 20
write_capacity = 20
attribute {
name = "LockID"
type = "S"
}
tags = {
Name = "Terraform DynamoDB State Lock Table"
}
}
module "dev_env" { module "dev_env" {
source = "./deployed_env" source = "./deployed_env"
environment = "dev" environment = "dev"
artifact_bucket = aws_s3_bucket.personal_measure artifact_bucket = aws_s3_bucket.personal_measure
cloudfront_ssl_certificate_arn = "arn:aws:acm:us-east-1:063932952339:certificate/48fe3ce0-4700-4eaa-b433-bb634f47934c" ecr_repo = aws_ecr_repository.personal_measure_api
} }
module "prod_env" { module "prod_env" {
source = "./deployed_env" source = "./deployed_env"
environment = "prod" environment = "prod"
artifact_bucket = aws_s3_bucket.personal_measure artifact_bucket = aws_s3_bucket.personal_measure
cloudfront_ssl_certificate_arn = "arn:aws:acm:us-east-1:063932952339:certificate/48fe3ce0-4700-4eaa-b433-bb634f47934c" ecr_repo = aws_ecr_repository.personal_measure_api
} }
data "aws_iam_policy_document" "cloudfront_access_policy" { data "aws_iam_policy_document" "cloudfront_access_policy" {
@ -45,6 +29,6 @@ data "aws_iam_policy_document" "cloudfront_access_policy" {
} }
resource "aws_s3_bucket_policy" "personal_measure" { resource "aws_s3_bucket_policy" "personal_measure" {
bucket = "${aws_s3_bucket.personal_measure.id}" bucket = aws_s3_bucket.personal_measure.id
policy = "${data.aws_iam_policy_document.cloudfront_access_policy.json}" policy = data.aws_iam_policy_document.cloudfront_access_policy.json
} }

View File

@ -1,8 +1,8 @@
terraform { terraform {
backend "s3" { backend "s3" {
bucket = "pm.jdb-labs.com" bucket = "pm.jdb-software.com"
region = "us-west-2" region = "us-west-2"
key = "terraform.tfstate" key = "terraform.tfstate"
dynamodb_table = "terraform-state-lock.pm.jdb-labs.com" dynamodb_table = "terraform-state-lock.jdb-software.com"
} }
} }

View File

@ -1,4 +1,4 @@
NODE_ENV=production NODE_ENV=production
VUE_APP_PM_API_BASE=https://pmapi-dev.jdb-labs.com/v0 VUE_APP_PM_API_BASE=https://pmapi-dev.jdb-software.com/v0
VUE_APP_LOG_LEVEL=INFO VUE_APP_LOG_LEVEL=INFO
VUE_APP_API_LOG_LEVEL=ERROR VUE_APP_API_LOG_LEVEL=ERROR

View File

@ -1,3 +1,3 @@
VUE_APP_PM_API_BASE=https://pmapi.jdb-labs.com/v0 VUE_APP_PM_API_BASE=https://pmapi.jdb-software.com/v0
VUE_APP_LOG_LEVEL=INFO VUE_APP_LOG_LEVEL=INFO
VUE_APP_API_LOG_LEVEL=ERROR VUE_APP_API_LOG_LEVEL=ERROR

340
web/package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "personal-measure-web", "name": "personal-measure-web",
"version": "0.9.0", "version": "0.11.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -829,30 +829,30 @@
} }
}, },
"@fortawesome/fontawesome-common-types": { "@fortawesome/fontawesome-common-types": {
"version": "0.2.27", "version": "0.2.31",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.27.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.31.tgz",
"integrity": "sha512-97GaByGaXDGMkzcJX7VmR/jRJd8h1mfhtA7RsxDBN61GnWE/PPCZhOdwG/8OZYktiRUF0CvFOr+VgRkJrt6TWg==" "integrity": "sha512-xfnPyH6NN5r/h1/qDYoGB0BlHSID902UkQzxR8QsoKDh55KAPr8ruAoie12WQEEQT8lRE2wtV7LoUllJ1HqCag=="
}, },
"@fortawesome/fontawesome-svg-core": { "@fortawesome/fontawesome-svg-core": {
"version": "1.2.27", "version": "1.2.31",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.27.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.31.tgz",
"integrity": "sha512-sOD3DKynocnHYpuw2sLPnTunDj7rLk91LYhi2axUYwuGe9cPCw7Bsu9EWtVdNJP+IYgTCZIbyARKXuy5K/nv+Q==", "integrity": "sha512-lqUWRK+ylHQJG5Kiez4XrAZAfc7snxCc+X59quL3xPfMnxzfyf1lt+/hD7X1ZL4KlzAH2KFzMuEVrolo/rAkog==",
"requires": { "requires": {
"@fortawesome/fontawesome-common-types": "^0.2.27" "@fortawesome/fontawesome-common-types": "^0.2.31"
} }
}, },
"@fortawesome/free-solid-svg-icons": { "@fortawesome/free-solid-svg-icons": {
"version": "5.12.1", "version": "5.15.0",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.12.1.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.0.tgz",
"integrity": "sha512-k3MwRFFUhyL4cuCJSaHDA0YNYMELDXX0h8JKtWYxO5XD3Dn+maXOMrVAAiNGooUyM2v/wz/TOaM0jxYVKeXX7g==", "integrity": "sha512-4dGRsOnGBPM7c0fd3LuiU6LgRSLn01rw1LJ370yC2iFMLUcLCLLynZhQbMhsiJmMwQM/YmPQblAdyHKVCgsIAA==",
"requires": { "requires": {
"@fortawesome/fontawesome-common-types": "^0.2.27" "@fortawesome/fontawesome-common-types": "^0.2.31"
} }
}, },
"@fortawesome/vue-fontawesome": { "@fortawesome/vue-fontawesome": {
"version": "0.1.9", "version": "0.1.10",
"resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.9.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-0.1.10.tgz",
"integrity": "sha512-h/emhmZz+DfB2zOGLWawNwXq82UYhn9waTfUjLLmeaIqtnIyNt6kYlpQT/vzJjLZRDRvY2IEJAh1di5qKpKVpA==" "integrity": "sha512-b2+SLF31h32LSepVcXe+BQ63yvbq5qmTCy4KfFogCYm2bn68H5sDWUnX+U7MBqnM2aeEk9M7xSoqGnu+wSdY6w=="
}, },
"@hapi/address": { "@hapi/address": {
"version": "2.1.4", "version": "2.1.4",
@ -1012,9 +1012,9 @@
"dev": true "dev": true
}, },
"@types/js-cookie": { "@types/js-cookie": {
"version": "2.2.4", "version": "2.2.6",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.4.tgz", "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz",
"integrity": "sha512-WTfSE1Eauak/Nrg6cA9FgPTFvVawejsai6zXoq0QYTQ3mxONeRtGhKxa7wMlUzWWmzrmTeV+rwLjHgsCntdrsA==" "integrity": "sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw=="
}, },
"@types/jwt-decode": { "@types/jwt-decode": {
"version": "2.2.1", "version": "2.2.1",
@ -1508,9 +1508,9 @@
"dev": true "dev": true
}, },
"@vue/test-utils": { "@vue/test-utils": {
"version": "1.0.0-beta.31", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.0.0-beta.31.tgz", "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.1.0.tgz",
"integrity": "sha512-IlhSx5hyEVnbvDZ3P98R1jNmy88QAd/y66Upn4EcvxSD5D4hwOutl3dIdfmSTSXs4b9DIMDnEVjX7t00cvOnvg==", "integrity": "sha512-M+3jtVqNYIrvzO5gaxogre5a5+96h0hN/dXw+5Lj0t+dp6fAhYcUjpLrC9j9cEEkl2Rcuh/gKYRUmR5N4vcqPw==",
"dev": true, "dev": true,
"requires": { "requires": {
"dom-event-types": "^1.0.0", "dom-event-types": "^1.0.0",
@ -1880,16 +1880,16 @@
"dev": true "dev": true
}, },
"apexcharts": { "apexcharts": {
"version": "3.15.6", "version": "3.21.0",
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.15.6.tgz", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.21.0.tgz",
"integrity": "sha512-8mZqg7eTZGU2zvjYUUOf+sTqgfmutipHU9lNgkqzZPtwIVGwR5PwXTBNKRJSI3AeSoQ8VZGYfzTJWoUDfGAeBw==", "integrity": "sha512-yeulUZCTG57swbJ5oIJIjgfRdIsvmC/2WJanrZxNGhjtZf2B9NaT95pEtbrml1BILJKtMn4VbpXVZp+8Tzmydg==",
"requires": { "requires": {
"svg.draggable.js": "^2.2.2", "svg.draggable.js": "^2.2.2",
"svg.easing.js": "^2.0.0", "svg.easing.js": "^2.0.0",
"svg.filter.js": "^2.0.2", "svg.filter.js": "^2.0.2",
"svg.pathmorphing.js": "^0.1.3", "svg.pathmorphing.js": "^0.1.3",
"svg.resize.js": "^1.4.3", "svg.resize.js": "^1.4.3",
"svg.select.js": "^2.1.2" "svg.select.js": "^3.0.1"
} }
}, },
"append-transform": { "append-transform": {
@ -6984,9 +6984,9 @@
} }
}, },
"globule": { "globule": {
"version": "1.3.0", "version": "1.3.2",
"resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz", "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz",
"integrity": "sha512-YlD4kdMqRCQHrhVdonet4TdRtv1/sZKepvoxNT4Nrhrp5HI8XFfc8kFlGlBn2myBo80aGp8Eft259mbcUJhgSg==", "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==",
"dev": true, "dev": true,
"requires": { "requires": {
"glob": "~7.1.1", "glob": "~7.1.1",
@ -7598,9 +7598,9 @@
"dev": true "dev": true
}, },
"in-publish": { "in-publish": {
"version": "2.0.0", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz", "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=", "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==",
"dev": true "dev": true
}, },
"indent-string": { "indent-string": {
@ -7674,12 +7674,6 @@
"loose-envify": "^1.0.0" "loose-envify": "^1.0.0"
} }
}, },
"invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
"dev": true
},
"ip": { "ip": {
"version": "1.1.5", "version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
@ -9276,9 +9270,9 @@
} }
}, },
"js-base64": { "js-base64": {
"version": "2.5.1", "version": "2.6.4",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
"integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==",
"dev": true "dev": true
}, },
"js-beautify": { "js-beautify": {
@ -9471,9 +9465,9 @@
"integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk="
}, },
"keen-ui": { "keen-ui": {
"version": "1.2.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/keen-ui/-/keen-ui-1.2.1.tgz", "resolved": "https://registry.npmjs.org/keen-ui/-/keen-ui-1.3.1.tgz",
"integrity": "sha512-SyF3orIjl098Du4b1UoXNDmdASxn/hsn7NO0JSoDI4LKmFUs8dP9uywfK+QEnDCev73jSZ3tdJELJKOjV/dl3Q==", "integrity": "sha512-2EAZy2YFdthCRtZvDHXvMZUTwvHda70WcjbEUaJKM1oH5q9rgecL80VBsTmmcIvfuratIEisBBiteojw3XEa5g==",
"requires": { "requires": {
"autosize": "^3.0.20", "autosize": "^3.0.20",
"deepmerge": "^2.0.1", "deepmerge": "^2.0.1",
@ -9526,15 +9520,6 @@
"launch-editor": "^2.2.1" "launch-editor": "^2.2.1"
} }
}, },
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
"integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
"dev": true,
"requires": {
"invert-kv": "^1.0.0"
}
},
"left-pad": { "left-pad": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
@ -10498,9 +10483,9 @@
} }
}, },
"moment": { "moment": {
"version": "2.24.0", "version": "2.29.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" "integrity": "sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA=="
}, },
"morgan": { "morgan": {
"version": "1.9.1", "version": "1.9.1",
@ -10786,9 +10771,9 @@
} }
}, },
"node-sass": { "node-sass": {
"version": "4.13.1", "version": "4.14.1",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz", "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz",
"integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==", "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==",
"dev": true, "dev": true,
"requires": { "requires": {
"async-foreach": "^0.1.3", "async-foreach": "^0.1.3",
@ -10805,7 +10790,7 @@
"node-gyp": "^3.8.0", "node-gyp": "^3.8.0",
"npmlog": "^4.0.0", "npmlog": "^4.0.0",
"request": "^2.88.0", "request": "^2.88.0",
"sass-graph": "^2.2.4", "sass-graph": "2.2.5",
"stdout-stream": "^1.4.0", "stdout-stream": "^1.4.0",
"true-case-path": "^1.0.2" "true-case-path": "^1.0.2"
}, },
@ -10856,9 +10841,9 @@
} }
}, },
"nan": { "nan": {
"version": "2.14.0", "version": "2.14.1",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==",
"dev": true "dev": true
}, },
"strip-ansi": { "strip-ansi": {
@ -12730,9 +12715,9 @@
} }
}, },
"register-service-worker": { "register-service-worker": {
"version": "1.6.2", "version": "1.7.1",
"resolved": "https://registry.npmjs.org/register-service-worker/-/register-service-worker-1.6.2.tgz", "resolved": "https://registry.npmjs.org/register-service-worker/-/register-service-worker-1.7.1.tgz",
"integrity": "sha512-I8L87fX2TK29LDx+wgyOUh2BJ3rDIRC1FtRZEHeP3rivzDv6p1DDZLGGtPucqjEkm45+2crtFIFssEWv56+9Wg==" "integrity": "sha512-IdTfUZ4u8iJL8o1w8es8l6UMGPmkwHolUdT+UmM1UypC80IB4KbpuIlvwWVj8UDS7eJwkEYRcKRgfRX+oTmJsw=="
}, },
"regjsgen": { "regjsgen": {
"version": "0.5.1", "version": "0.5.1",
@ -13066,118 +13051,137 @@
} }
}, },
"sass-graph": { "sass-graph": {
"version": "2.2.4", "version": "2.2.5",
"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz", "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz",
"integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=", "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==",
"dev": true, "dev": true,
"requires": { "requires": {
"glob": "^7.0.0", "glob": "^7.0.0",
"lodash": "^4.0.0", "lodash": "^4.0.0",
"scss-tokenizer": "^0.2.3", "scss-tokenizer": "^0.2.3",
"yargs": "^7.0.0" "yargs": "^13.3.2"
}, },
"dependencies": { "dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
},
"camelcase": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
"integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
"dev": true
},
"cliui": { "cliui": {
"version": "3.2.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"dev": true, "dev": true,
"requires": { "requires": {
"string-width": "^1.0.1", "string-width": "^3.1.0",
"strip-ansi": "^3.0.1", "strip-ansi": "^5.2.0",
"wrap-ansi": "^2.0.0" "wrap-ansi": "^5.1.0"
} }
}, },
"is-fullwidth-code-point": { "emoji-regex": {
"version": "1.0.0", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"dev": true, "dev": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "locate-path": "^3.0.0"
} }
}, },
"os-locale": { "get-caller-file": {
"version": "1.4.0", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true
},
"locate-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"dev": true, "dev": true,
"requires": { "requires": {
"lcid": "^1.0.0" "p-locate": "^3.0.0",
"path-exists": "^3.0.0"
} }
}, },
"p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-locate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"dev": true,
"requires": {
"p-limit": "^2.0.0"
}
},
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"require-main-filename": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
"dev": true
},
"string-width": { "string-width": {
"version": "1.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"dev": true, "dev": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^3.0.0" "strip-ansi": "^5.1.0"
} }
}, },
"strip-ansi": { "wrap-ansi": {
"version": "3.0.1", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-styles": "^3.2.0",
"string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
} }
}, },
"which-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
"integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
"dev": true
},
"y18n": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
"dev": true
},
"yargs": { "yargs": {
"version": "7.1.0", "version": "13.3.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
"integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==",
"dev": true, "dev": true,
"requires": { "requires": {
"camelcase": "^3.0.0", "cliui": "^5.0.0",
"cliui": "^3.2.0", "find-up": "^3.0.0",
"decamelize": "^1.1.1", "get-caller-file": "^2.0.1",
"get-caller-file": "^1.0.1",
"os-locale": "^1.4.0",
"read-pkg-up": "^1.0.1",
"require-directory": "^2.1.1", "require-directory": "^2.1.1",
"require-main-filename": "^1.0.1", "require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0", "set-blocking": "^2.0.0",
"string-width": "^1.0.2", "string-width": "^3.0.0",
"which-module": "^1.0.0", "which-module": "^2.0.0",
"y18n": "^3.2.1", "y18n": "^4.0.0",
"yargs-parser": "^5.0.0" "yargs-parser": "^13.1.2"
} }
}, },
"yargs-parser": { "yargs-parser": {
"version": "5.0.0", "version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true, "dev": true,
"requires": { "requires": {
"camelcase": "^3.0.0" "camelcase": "^5.0.0",
"decamelize": "^1.2.0"
} }
} }
} }
@ -13415,6 +13419,12 @@
} }
} }
}, },
"servor": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/servor/-/servor-4.0.2.tgz",
"integrity": "sha512-MlmQ5Ntv4jDYUN060x/KEmN7emvIqKMZ9OkM+nY8Bf2+KkyLmGsTqWLyAN2cZr5oESAcH00UanUyyrlS1LRjFw==",
"dev": true
},
"set-blocking": { "set-blocking": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@ -14279,14 +14289,24 @@
"requires": { "requires": {
"svg.js": "^2.6.5", "svg.js": "^2.6.5",
"svg.select.js": "^2.1.2" "svg.select.js": "^2.1.2"
},
"dependencies": {
"svg.select.js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
"requires": {
"svg.js": "^2.2.5"
}
}
} }
}, },
"svg.select.js": { "svg.select.js": {
"version": "2.1.2", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
"requires": { "requires": {
"svg.js": "^2.2.5" "svg.js": "^2.6.5"
} }
}, },
"svgo": { "svgo": {
@ -14890,9 +14910,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "3.7.5", "version": "3.9.7",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
"dev": true "dev": true
}, },
"uglify-js": { "uglify-js": {
@ -15206,14 +15226,14 @@
"dev": true "dev": true
}, },
"vue": { "vue": {
"version": "2.6.11", "version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.11.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-2.6.12.tgz",
"integrity": "sha512-VfPwgcGABbGAue9+sfrD4PuwFar7gPb1yl1UK1MwXoQPAw0BKSqWfoYCT/ThFrdEVWoI51dBuyCoiNU9bZDZxQ==" "integrity": "sha512-uhmLFETqPPNyuLLbsKz6ioJ4q7AZHzD8ZVFNATNyICSZouqP2Sz0rotWQC8UNBF6VGSCs5abnKJoStA6JbCbfg=="
}, },
"vue-apexcharts": { "vue-apexcharts": {
"version": "1.5.2", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.5.2.tgz", "resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz",
"integrity": "sha512-m7IIyql4yU6cLTu5RODx3DcdxCekmNRzUh7lEoybq2MXcgabmBPhUn8qgXNx1HucWiMNOdXfwq/L6TfCbKnfMw==" "integrity": "sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g=="
}, },
"vue-class-component": { "vue-class-component": {
"version": "6.3.2", "version": "6.3.2",
@ -15275,9 +15295,9 @@
} }
}, },
"vue-router": { "vue-router": {
"version": "3.1.5", "version": "3.4.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.1.5.tgz", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.5.tgz",
"integrity": "sha512-BszkPvhl7I9h334GjckCh7sVFyjTPMMJFJ4Bsrem/Ik+B/9gt5tgrk8k4gGLO4ZpdvciVdg7O41gW4DisQWurg==" "integrity": "sha512-ioRY5QyDpXM9TDjOX6hX79gtaMXSVDDzSlbIlyAmbHNteIL81WIVB2e+jbzV23vzxtoV0krdS2XHm+GxFg+Nxg=="
}, },
"vue-style-loader": { "vue-style-loader": {
"version": "4.1.2", "version": "4.1.2",
@ -15290,9 +15310,9 @@
} }
}, },
"vue-template-compiler": { "vue-template-compiler": {
"version": "2.6.11", "version": "2.6.12",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz",
"integrity": "sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA==", "integrity": "sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg==",
"dev": true, "dev": true,
"requires": { "requires": {
"de-indent": "^1.0.2", "de-indent": "^1.0.2",
@ -15314,9 +15334,9 @@
} }
}, },
"vuex": { "vuex": {
"version": "3.1.2", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.2.tgz", "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.5.1.tgz",
"integrity": "sha512-ha3jNLJqNhhrAemDXcmMJMKf1Zu4sybMPr9KxJIuOpVcsDQlTBYLLladav2U+g1AvdYDG5Gs0xBTb0M5pXXYFQ==" "integrity": "sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw=="
}, },
"vuex-module-decorators": { "vuex-module-decorators": {
"version": "0.9.11", "version": "0.9.11",

View File

@ -1,43 +1,43 @@
{ {
"name": "personal-measure-web", "name": "personal-measure-web",
"version": "0.9.0", "version": "0.11.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "npx servor dist",
"build-prod": "vue-cli-service build --mode production", "build-prod": "vue-cli-service build --mode production",
"build-dev": "vue-cli-service build --mode development", "build-dev": "vue-cli-service build --mode development",
"lint": "vue-cli-service lint", "lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit" "test:unit": "vue-cli-service test:unit"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.27", "@fortawesome/fontawesome-svg-core": "^1.2.31",
"@fortawesome/free-solid-svg-icons": "^5.12.1", "@fortawesome/free-solid-svg-icons": "^5.15.0",
"@fortawesome/vue-fontawesome": "^0.1.9", "@fortawesome/vue-fontawesome": "^0.1.10",
"@types/js-cookie": "^2.2.4", "@types/js-cookie": "^2.2.6",
"@types/jwt-decode": "^2.2.1", "@types/jwt-decode": "^2.2.1",
"@types/lodash.assign": "^4.2.6", "@types/lodash.assign": "^4.2.6",
"@types/lodash.findindex": "^4.6.6", "@types/lodash.findindex": "^4.6.6",
"@types/lodash.merge": "^4.6.6", "@types/lodash.merge": "^4.6.6",
"@types/lodash.omit": "^4.5.6", "@types/lodash.omit": "^4.5.6",
"apexcharts": "^3.15.6", "apexcharts": "^3.21.0",
"axios": "^0.18.1", "axios": "^0.18.1",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"jwt-decode": "^2.2.0", "jwt-decode": "^2.2.0",
"keen-ui": "^1.2.1", "keen-ui": "^1.3.1",
"lodash.assign": "^4.2.0", "lodash.assign": "^4.2.0",
"lodash.findindex": "^4.6.0", "lodash.findindex": "^4.6.0",
"lodash.keyby": "^4.6.0", "lodash.keyby": "^4.6.0",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"lodash.omit": "^4.5.0", "lodash.omit": "^4.5.0",
"moment": "^2.24.0", "moment": "^2.29.0",
"register-service-worker": "^1.5.2", "register-service-worker": "^1.7.1",
"vue": "^2.6.11", "vue": "^2.6.12",
"vue-apexcharts": "^1.5.2", "vue-apexcharts": "^1.6.0",
"vue-class-component": "^6.0.0", "vue-class-component": "^6.0.0",
"vue-property-decorator": "^7.0.0", "vue-property-decorator": "^7.0.0",
"vue-router": "^3.1.5", "vue-router": "^3.4.5",
"vuejs-smart-table": "0.0.3", "vuejs-smart-table": "0.0.3",
"vuex": "^3.1.2", "vuex": "^3.5.1",
"vuex-module-decorators": "^0.9.11" "vuex-module-decorators": "^0.9.11"
}, },
"devDependencies": { "devDependencies": {
@ -48,16 +48,17 @@
"@vue/cli-plugin-typescript": "^3.12.1", "@vue/cli-plugin-typescript": "^3.12.1",
"@vue/cli-plugin-unit-jest": "^3.12.1", "@vue/cli-plugin-unit-jest": "^3.12.1",
"@vue/cli-service": "^3.12.1", "@vue/cli-service": "^3.12.1",
"@vue/test-utils": "^1.0.0-beta.31", "@vue/test-utils": "^1.1.0",
"babel-core": "7.0.0-bridge.0", "babel-core": "7.0.0-bridge.0",
"lint-staged": "^8.2.1", "lint-staged": "^8.2.1",
"live-server": "^1.2.1", "live-server": "^1.2.1",
"node-sass": "^4.13.1", "node-sass": "^4.14.1",
"sass-loader": "^7.3.1", "sass-loader": "^7.3.1",
"servor": "^4.0.2",
"ts-jest": "^23.0.0", "ts-jest": "^23.0.0",
"typescript": "^3.7.5", "typescript": "^3.9.7",
"vue-cli-plugin-webpack-bundle-analyzer": "^1.4.0", "vue-cli-plugin-webpack-bundle-analyzer": "^1.4.0",
"vue-template-compiler": "^2.6.11" "vue-template-compiler": "^2.6.12"
}, },
"gitHooks": { "gitHooks": {
"pre-commit": "lint-staged" "pre-commit": "lint-staged"