Merge pull request #3 from meienberger/feature/install-app

Feature/install app
This commit is contained in:
Nicolas Meienberger 2022-04-21 20:27:50 +00:00 committed by GitHub
commit 206b822cac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
106 changed files with 2915 additions and 518 deletions

1
README.md Normal file
View file

@ -0,0 +1 @@
[![forthebadge](https://svgshare.com/i/g4Y.svg)](https://forthebadge.com)

View file

@ -5,9 +5,8 @@ packages:
- iptables - iptables
- coreutils - coreutils
- git - git
- base-devel
- docker - docker
- avahi - avahi-daemon
- nodejs - nodejs
- npm - npm
@ -15,4 +14,4 @@ username: nicolas
### ZSH Settings ### ZSH Settings
zsh_theme: "powerlevel10k/powerlevel10k" zsh_theme: "powerlevel10k/powerlevel10k"
ohmyzsh_git_url: https://github.com/robbyrussell/oh-my-zsh ohmyzsh_git_url: https://github.com/robbyrussell/oh-my-zsh

View file

@ -1,8 +1,33 @@
- name: Install docker - name: Install docker
package: package:
name: docker name:
- docker
- ca-certificates
- curl
- gnupg
- lsb-release
state: latest state: latest
- name: Add docker gpg key
shell: curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
- name: Check lsb_release -cs
shell: lsb_release -cs
register: lsb_release
- name: Add deb for bookworm release
shell: echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bullseye stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
when: lsb_release.stdout == "bookworm"
- name: Add deb for non-bookworm
shell: echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
when: lsb_release.stdout != "bookworm"
- name: Update packages
apt:
update_cache: yes
upgrade: yes
- name: Install essential packages - name: Install essential packages
package: package:
name: name:
@ -24,6 +49,20 @@
- name: Make docker-compose executable - name: Make docker-compose executable
shell: chmod +x /usr/local/bin/docker-compose shell: chmod +x /usr/local/bin/docker-compose
# - name: Disable iptables for docker by editing file /etc/default/docker
# lineinfile:
# path: /etc/default/docker
# regexp: "^DOCKER_OPTS="
# line: "DOCKER_OPTS=\"--iptables=false\""
# state: present
# - name: Create file /etc/docker/daemon.json with content hello world written inside
# lineinfile:
# path: /etc/docker/daemon.json
# regexp: "^"
# line: "{ \"iptables\": false }"
# state: present
- name: Create group docker - name: Create group docker
group: group:
name: docker name: docker

View file

@ -1,22 +1,21 @@
- name: Change machine hostname to tipi.local - name: Change machine hostname to tipi.local
shell: hostnamectl set-hostname tipi.local shell: hostnamectl set-hostname tipi.local
- name: Update packages # - name: Update packages
become: yes # apt:
pacman: # update_cache: yes
update_cache: yes # upgrade: yes
upgrade: yes
- name: Add user to root group
user:
name: "{{ username }}"
group: root
- name: Install essential packages - name: Install essential packages
package: package:
name: "{{ packages }}" name: "{{ packages }}"
state: latest state: latest
- name: Add user to root group
user:
name: "{{ username }}"
group: root
- name: Disable SSH password auth - name: Disable SSH password auth
lineinfile: lineinfile:
dest: /etc/ssh/sshd_config dest: /etc/ssh/sshd_config

View file

@ -1,6 +1,6 @@
- name: Install "pm2" package globally. - name: Install "pm2" package globally.
community.general.npm: community.general.npm:
name: yarn name: pm2
global: yes global: yes
- name: Run pm2 first time - name: Run pm2 first time

45
apps/anonaddy/config.json Normal file
View file

@ -0,0 +1,45 @@
{
"name": "Anonaddy",
"port": 8084,
"id": "anonaddy",
"description": "",
"short_desc": "Anonymous email forwarding",
"author": "",
"source": "https://github.com/anonaddy/anonaddy",
"image": "https://avatars.githubusercontent.com/u/51450862?s=200&v=4",
"requirements": {
"ports": [25]
},
"form_fields": {
"username": {
"type": "text",
"label": "Username",
"required": true,
"env_variable": "ANONADDY_USERNAME"
},
"key": {
"type": "text",
"label": "App key",
"hint": "Application key for encrypter service. Generate one with : echo \"base64:$(openssl rand -base64 32)\"",
"required": true,
"env_variable": "ANONADDY_KEY"
},
"domain": {
"type": "fqdn",
"label": "Your email domain (eg. example.com)",
"max": 50,
"min": 3,
"required": true,
"env_variable": "ANONADDY_DOMAIN"
},
"secret": {
"type": "text",
"label": "App secret",
"hint": "Long random string used when hashing data for the anonymous replies",
"max": 50,
"min": 3,
"required": true,
"env_variable": "ANONADDY_SECRET"
}
}
}

View file

@ -32,29 +32,32 @@ services:
container_name: anonaddy container_name: anonaddy
ports: ports:
- 25:25 - 25:25
- ${APP_ANONADDY_PORT}:8000 - ${APP_PORT}:8000
depends_on: depends_on:
- db - db
- redis - redis
volumes: volumes:
- "${APP_DATA_DIR}/data:/data" - "${APP_DATA_DIR}/data:/data"
environment: environment:
TZ: ${TZ}
DB_HOST: db-anonaddy DB_HOST: db-anonaddy
DB_PASSWORD: anonaddy DB_PASSWORD: anonaddy
REDIS_HOST: redis-anonaddy REDIS_HOST: redis-anonaddy
APP_KEY: ${APP_ANONADDY_KEY} APP_KEY: ${ANONADDY_KEY}
ANONADDY_DOMAIN: ${APP_ANONADDY_DOMAIN} ANONADDY_DOMAIN: ${ANONADDY_DOMAIN}
ANONADDY_SECRET: ${APP_ANONADDY_SECRET} ANONADDY_SECRET: ${ANONADDY_SECRET}
ANONADDY_ADMIN_USERNAME: ${ANONADDY_USERNAME}
POSTFIX_DEBUG: true
restart: unless-stopped restart: unless-stopped
networks: networks:
- tipi_main_network - tipi_main_network
labels: # labels:
traefik.enable: true # traefik.enable: true
traefik.http.routers.anonaddy.rule: Host(`anonaddy.tipi.home`) # traefik.http.routers.anonaddy.rule: Host(`anonaddy.tipi.home`)
traefik.http.routers.anonaddy.tls: true # traefik.http.routers.anonaddy.tls: true
traefik.http.routers.anonaddy.entrypoints: websecure # traefik.http.routers.anonaddy.entrypoints: websecure
traefik.http.routers.anonaddy.service: anonaddy # traefik.http.routers.anonaddy.service: anonaddy
traefik.http.services.anonaddy.loadbalancer.server.port: 8000 # traefik.http.services.anonaddy.loadbalancer.server.port: 8000
# labels: # labels:
# traefik.enable: true # traefik.enable: true
# traefik.http.routers.anonaddy.rule: PathPrefix(`/anonaddy`) # traefik.http.routers.anonaddy.rule: PathPrefix(`/anonaddy`)

11
apps/busybox/config.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "BusyBox",
"port": 3000,
"id": "busybox",
"description": "",
"short_desc": "",
"author": "",
"source": "",
"image": "https://raw.githubusercontent.com/docker-library/docs/cc5d5e47fd7e0c57c9b8de4c1bfb6258e0dac85d/busybox/logo.png",
"form_fields": {}
}

View file

@ -0,0 +1,6 @@
version: "3.7"
services:
test:
image: meienberger/ubuntu-test
networks:
- tipi_main_network

View file

@ -2,5 +2,4 @@ version: "3.7"
networks: networks:
tipi_main_network: tipi_main_network:
external:
name: runtipi_tipi_main_network name: runtipi_tipi_main_network

11
apps/filerun/config.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "FileRun",
"port": 8087,
"id": "filerun",
"description": "Reliable and Performant File Management Desktop Sync and File Sharing",
"short_desc": "Access your homeserver files from your browser",
"author": "FileRun, LDA - Portugal",
"source": "https://www.filerun.com/",
"image": "https://avatars.githubusercontent.com/u/6422152?v=4",
"form_fields": {}
}

View file

@ -0,0 +1,38 @@
services:
filerun-db:
container_name: filerun-db
image: mariadb:10.1
environment:
MYSQL_ROOT_PASSWORD: tipi
MYSQL_USER: tipi
MYSQL_PASSWORD: tipi
MYSQL_DATABASE: tipi
volumes:
- ${APP_DATA_DIR}/data/db:/var/lib/mysql
networks:
- tipi_main_network
filerun:
container_name: filerun
image: filerun/filerun:arm64v8
environment:
FR_DB_HOST: filerun-db
FR_DB_PORT: 3306
FR_DB_NAME: tipi
FR_DB_USER: tipi
FR_DB_PASS: tipi
APACHE_RUN_USER: ${PUID}
APACHE_RUN_GROUP: ${PGID}
APACHE_RUN_USER_ID: 33
APACHE_RUN_GROUP_ID: 33
depends_on:
- db
links:
- db:db
ports:
- ${APP_PORT}:80
volumes:
- ${APP_DATA_DIR}/data/html:/var/www/html
- ${ROOT_FOLDER}/app-data:/user-files
networks:
- tipi_main_network

11
apps/freshrss/config.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "FreshRSS",
"port": 8086,
"id": "freshrss",
"description": "FreshRSS is a self-hosted RSS feed aggregator like Leed or Kriss Feed.\nIt is lightweight, easy to work with, powerful, and customizable.\n\nIt is a multi-user application with an anonymous reading mode. It supports custom tags. There is an API for (mobile) clients, and a Command-Line Interface.\n\nThanks to the WebSub standard (formerly PubSubHubbub), FreshRSS is able to receive instant push notifications from compatible sources, such as Mastodon, Friendica, WordPress, Blogger, FeedBurner, etc.\n\nFreshRSS natively supports basic Web scraping, based on XPath, for Web sites not providing any RSS / Atom feed.\n\nFinally, it supports extensions for further tuning.",
"short_desc": "A free, self-hostable aggregator… ",
"author": "https://freshrss.org/",
"source": "https://github.com/FreshRSS/FreshRSS",
"image": "https://avatars.githubusercontent.com/u/9414285?s=200&v=4",
"form_fields": {}
}

View file

@ -6,19 +6,19 @@ services:
image: freshrss/freshrss:arm image: freshrss/freshrss:arm
restart: unless-stopped restart: unless-stopped
ports: ports:
- "${APP_FRESHRSS_PORT}:80" - ${APP_PORT}:80
volumes: volumes:
- ${APP_DATA_DIR}/data/:/var/www/FreshRSS/data - ${APP_DATA_DIR}/data/freshrss:/var/www/FreshRSS/data
- ${APP_DATA_DIR}/extensions/:/var/www/FreshRSS/extensions - ${APP_DATA_DIR}/data/extensions/:/var/www/FreshRSS/extensions
environment: environment:
CRON_MIN: '*/20' CRON_MIN: '*/20'
TZ: $TZ TZ: $TZ
networks: networks:
- tipi_main_network - tipi_main_network
labels: # labels:
traefik.enable: true # traefik.enable: true
traefik.http.routers.freshrss.rule: Host(`freshrss.tipi.home`) # traefik.http.routers.freshrss.rule: Host(`freshrss.tipi.home`)
traefik.http.routers.freshrss.service: freshrss # traefik.http.routers.freshrss.service: freshrss
traefik.http.routers.freshrss.tls: true # traefik.http.routers.freshrss.tls: true
traefik.http.routers.freshrss.entrypoints: websecure # traefik.http.routers.freshrss.entrypoints: websecure
traefik.http.services.freshrss.loadbalancer.server.port: 80 # traefik.http.services.freshrss.loadbalancer.server.port: 80

11
apps/jellyfin/config.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "Jellyfin",
"port": 8091,
"id": "jellyfin",
"description": "",
"short_desc": "",
"author": "",
"source": "",
"image": "https://avatars.githubusercontent.com/u/45698031?s=200&v=4",
"form_fields": {}
}

View file

@ -0,0 +1,18 @@
version: "3.7"
services:
jellyfin:
image: lscr.io/linuxserver/jellyfin
container_name: jellyfin
volumes:
- ${APP_DATA_DIR}/data/config:/config
- ${APP_DATA_DIR}/data/media:/data/media
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
restart: "unless-stopped"
ports:
- ${APP_PORT}:8096
networks:
- tipi_main_network

View file

@ -1,6 +1,12 @@
{ {
"name": "Nextcloud", "name": "Nextcloud",
"port": 8083,
"id": "nextcloud",
"description": "Nextcloud is a self-hosted, open source, and fully-featured cloud storage solution for your personal files, office documents, and photos.", "description": "Nextcloud is a self-hosted, open source, and fully-featured cloud storage solution for your personal files, office documents, and photos.",
"short_desc": "Productivity platform that keeps you in control",
"author": "Nextcloud GmbH",
"source": "https://github.com/nextcloud/server",
"image": "https://avatars.githubusercontent.com/u/19211038?s=200&v=4",
"form_fields": { "form_fields": {
"username": { "username": {
"type": "text", "type": "text",
@ -8,7 +14,7 @@
"max": 50, "max": 50,
"min": 3, "min": 3,
"required": true, "required": true,
"env_variable": "NEXTCLOUD_USERNAME" "env_variable": "NEXTCLOUD_ADMIN_USER"
}, },
"password": { "password": {
"type": "password", "type": "password",
@ -16,7 +22,7 @@
"max": 50, "max": 50,
"min": 3, "min": 3,
"required": true, "required": true,
"env_variable": "NEXTCLOUD_PASSWORD" "env_variable": "NEXTCLOUD_ADMIN_PASSWORD"
} }
} }
} }

View file

@ -3,24 +3,21 @@ version: "3.7"
services: services:
db-nextcloud: db-nextcloud:
container_name: db-nextcloud container_name: db-nextcloud
# user: '1000:1000' image: postgres:14.2
image: mariadb:10.5.12
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
restart: on-failure restart: on-failure
volumes: volumes:
- ${APP_DATA_DIR}/data/db:/var/lib/mysql - ${APP_DATA_DIR}/data/db:/var/lib/postgresql/data
environment: environment:
- MYSQL_ROOT_PASSWORD=password - POSTGRES_PASSWORD=tipi
- MYSQL_PASSWORD=password - POSTGRES_USER=tipi
- MYSQL_DATABASE=nextcloud - POSTGRES_DB=nextcloud
- MYSQL_USER=nextcloud
networks: networks:
- tipi_main_network - tipi_main_network
redis-nextcloud: redis-nextcloud:
container_name: redis-nextcloud container_name: redis-nextcloud
# user: '1000:1000' user: "1000:1000"
image: redis:6.2.2-buster image: redis:6.2.6
restart: on-failure restart: on-failure
volumes: volumes:
- "${APP_DATA_DIR}/data/redis:/data" - "${APP_DATA_DIR}/data/redis:/data"
@ -28,7 +25,7 @@ services:
- tipi_main_network - tipi_main_network
cron: cron:
image: nextcloud:22.0.0-apache image: nextcloud:23.0.3-apache
restart: on-failure restart: on-failure
volumes: volumes:
- ${APP_DATA_DIR}/data/nextcloud:/var/www/html - ${APP_DATA_DIR}/data/nextcloud:/var/www/html
@ -40,23 +37,23 @@ services:
- tipi_main_network - tipi_main_network
nextcloud: nextcloud:
user: root
container_name: nextcloud container_name: nextcloud
image: nextcloud:22.1.1-apache image: nextcloud:23.0.3-apache
restart: unless-stopped restart: unless-stopped
ports: ports:
- ${APP_NEXTCLOUD_PORT}:80 - ${APP_PORT}:80
volumes: volumes:
- ${APP_DATA_DIR}/data/nextcloud:/var/www/html - ${APP_DATA_DIR}/data/nextcloud:/var/www/html
- /volumes/nfs:/nfs
environment: environment:
- MYSQL_HOST=db-nextcloud - POSTGRES_HOST=db-nextcloud
- REDIS_HOST=redis-nextcloud - REDIS_HOST=redis-nextcloud
- MYSQL_PASSWORD=password - POSTGRES_PASSWORD=tipi
- MYSQL_DATABASE=nextcloud - POSTGRES_USER=tipi
- MYSQL_USER=nextcloud - POSTGRES_DB=nextcloud
- NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER} - NEXTCLOUD_ADMIN_USER=${NEXTCLOUD_ADMIN_USER}
- NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD} - NEXTCLOUD_ADMIN_PASSWORD=${NEXTCLOUD_ADMIN_PASSWORD}
- NEXTCLOUD_TRUSTED_DOMAINS=tipi.local - NEXTCLOUD_TRUSTED_DOMAINS=tipi.local ${DEVICE_IP}:${APP_PORT}
depends_on: depends_on:
- db-nextcloud - db-nextcloud
- redis-nextcloud - redis-nextcloud

View file

@ -1,24 +0,0 @@
## DNS Over TLS, Simple ENCRYPTED recursive caching DNS, TCP port 853
## unbound.conf, original at https://calomel.org/unbound_dns.html
# tweaks by bartonbytes.com
server:
access-control: 127.0.0.0/8 allow
cache-max-ttl: 14400
cache-min-ttl: 600
do-tcp: yes
hide-identity: yes
hide-version: yes
interface: 127.0.0.1
minimal-responses: yes
prefetch: yes
qname-minimisation: yes
rrset-roundrobin: yes
ssl-upstream: yes
use-caps-for-id: yes
verbosity: 1
port: 5533
#
forward-zone:
name: "."
forward-addr: 194.242.2.3@853 # Mullvad primary
forward-addr: 193.19.108.3@853 # Mullvad secondary

View file

@ -1,34 +0,0 @@
version: "3.7"
services:
unbound:
user: '1000:1000'
image: "klutchell/unbound:latest"
volumes:
- ${APP_DATA_DIR}/data/unbound:/etc/unbound
networks:
- tipi_main_network
pihole:
image: pihole/pihole
restart: on-failure
ports:
- 53:53
- 53:53/udp
- ${APP_PI_HOLE_PORT}:80
volumes:
- ${APP_DATA_DIR}/data/pihole:/etc/pihole/
- ${APP_DATA_DIR}/data/dnsmasq:/etc/dnsmasq.d/
environment:
- VIRTUAL_HOST="pihole.${DOMAIN}"
- WEBPASSWORD=${APP_PASSWORD}
- PIHOLE_DNS=unbound
depends_on:
- unbound
networks:
- tipi_main_network
labels:
traefik.enable: true
traefik.http.routers.traefik.rule: Host(`pihole.${DOMAIN}`)
traefik.http.services.traefik.loadbalancer.server.port: $APP_PI_HOLE_PORT

23
apps/pihole/config.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "PiHole",
"port": 8081,
"requirements": {
"ports": [53]
},
"id": "pihole",
"description": "",
"short_desc": "",
"author": "",
"source": "",
"image": "https://avatars.githubusercontent.com/u/16827203?s=200&v=4",
"form_fields": {
"password": {
"type": "password",
"label": "Password",
"max": 50,
"min": 3,
"required": true,
"env_variable": "APP_PASSWORD"
}
}
}

View file

@ -0,0 +1,24 @@
version: "3.7"
services:
pihole:
container_name: pihole
image: cbcrowe/pihole-unbound:latest
restart: unless-stopped
ports:
- 53:53/tcp
- 53:53/udp
- ${APP_PORT}:80
volumes:
- ${APP_DATA_DIR}/data/pihole:/etc/pihole
- ${APP_DATA_DIR}/data/dnsmasq:/etc/dnsmasq.d
environment:
TZ: ${TZ}
WEBPASSWORD: ${APP_PASSWORD}
PIHOLE_DNS_: 127.0.0.1#5335
FTLCONF_REPLY_ADDR4: 192.168.2.132
PIHOLE_DNS_: 127.0.0.1#5335
DNSSEC: "true"
DNSMASQ_LISTENING: single
networks:
- tipi_main_network

20
apps/radarr/config.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "Radarr",
"port": 8088,
"id": "radarr",
"description": "",
"short_desc": "",
"author": "",
"source": "",
"image": "https://avatars.githubusercontent.com/u/25025331?s=200&v=4",
"form_fields": {
"torrent-client": {
"type": "text",
"label": "Torrent Client",
"max": 50,
"min": 3,
"required": true,
"env_variable": "TORRENT_CLIENT"
}
}
}

View file

@ -0,0 +1,35 @@
version: "3.7"
services:
jackett:
image: lscr.io/linuxserver/jackett
container_name: jackett
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- AUTO_UPDATE=true
volumes:
- ${APP_DATA_DIR}/data/config:/config
- ${APP_DATA_DIR}/data/downloads:/downloads
ports:
- 9117:9117
restart: unless-stopped
networks:
- tipi_main_network
radarr:
image: lscr.io/linuxserver/radarr
container_name: radarr
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
volumes:
- ${APP_DATA_DIR}/data/config:/config
- ${APP_DATA_DIR}/data/movies:/movies #optional
- ${ROOT_FOLDER}/app-data/${TORRENT_CLIENT}/data/downloads:/downloads #optional
ports:
- ${APP_PORT}:7878
restart: unless-stopped
networks:
- tipi_main_network

View file

@ -0,0 +1,11 @@
{
"name": "Simple Torrent",
"port": 8085,
"id": "simple-torrent",
"description": "SimpleTorrent is a a self-hosted remote torrent client, written in Go (golang). Started torrents remotely, download sets of files on the local disk of the server, which are then retrievable or streamable via HTTP.",
"short_desc": "A self-hosted remote torrent client",
"author": "",
"source": "https://github.com/boypt/simple-torrent",
"image": "https://getumbrel.github.io/umbrel-apps-gallery/simple-torrent/icon.svg",
"form_fields": {}
}

View file

@ -6,9 +6,9 @@ services:
image: boypt/cloud-torrent:1.3.9 image: boypt/cloud-torrent:1.3.9
restart: on-failure restart: on-failure
ports: ports:
- "${APP_SIMPLETORRENT_PORT}:${APP_SIMPLETORRENT_PORT}" - ${APP_PORT}:${APP_PORT}
command: > command: >
--port=${APP_SIMPLETORRENT_PORT} --port=${APP_PORT}
--config-path /config/simple-torrent.json --config-path /config/simple-torrent.json
volumes: volumes:
- ${APP_DATA_DIR}/data/torrents:/torrents - ${APP_DATA_DIR}/data/torrents:/torrents

View file

@ -0,0 +1,12 @@
{
"name": "Syncthing",
"port": 8090,
"id": "syncthing",
"description": "Syncthing is a peer-to-peer continuous file synchronization program. It synchronizes files between two or more computers in real time, safely protected from prying eyes. Your data is your data alone and you deserve to choose where it is stored, whether it is shared with some third party, and how it's transmitted over the internet.\n\nInstall the Syncthing app on your Umbrel and pair it with the Syncthing app on your phone or computer for a self hosted peer-to-peer backup solution.",
"short_desc": "Peer-to-peer file synchronization between your devices",
"author": "The Syncthing Foundation",
"source": "https://github.com/syncthing",
"website": "https://syncthing.net",
"image": "https://avatars.githubusercontent.com/u/7628018?s=200&v=4",
"form_fields": {}
}

View file

@ -0,0 +1,20 @@
version: "3.7"
services:
server:
image: syncthing/syncthing:1.19
stop_grace_period: 1m
hostname: tipi
environment:
- PUID=1000
- PGID=1000
volumes:
- ${APP_DATA_DIR}/data:/var/syncthing
ports:
- ${APP_PORT}:8384
- 22000:22000/tcp # TCP file transfers
- 22000:22000/udp # QUIC file transfers
- 21027:21027/udp # Receive local discovery broadcasts
restart: unless-stopped
networks:
- tipi_main_network

View file

@ -1,8 +0,0 @@
version: '3.7'
services:
test:
build:
context: .
dockerfile: Dockerfile
networks:
- tipi_main_network

View file

@ -0,0 +1,31 @@
{
"name": "Transmission",
"port": 8089,
"requirements": {
"ports": [51413]
},
"id": "transmission",
"description": "",
"short_desc": "",
"author": "",
"source": "https://transmissionbt.com",
"image": "https://avatars.githubusercontent.com/u/223312?s=200&v=4",
"form_fields": {
"username": {
"type": "text",
"label": "Username",
"max": 50,
"min": 3,
"required": true,
"env_variable": "TRANSMISSION_USERNAME"
},
"password": {
"type": "password",
"label": "Password",
"max": 50,
"min": 3,
"required": true,
"env_variable": "TRANSMISSION_PASSWORD"
}
}
}

View file

View file

View file

@ -0,0 +1,25 @@
version: "3.7"
services:
transmission:
image: lscr.io/linuxserver/transmission
container_name: transmission
environment:
- PUID=1000
- PGID=1000
- TZ=${TZ}
- USER=${TRANSMISSION_USERNAME}
- PASS=${TRANSMISSION_PASSWORD}
# - WHITELIST=iplist #optional
# - PEERPORT=peerport #optional
# - HOST_WHITELIST=dnsnane list #optional
volumes:
- ${APP_DATA_DIR}/data/config:/config
- ${APP_DATA_DIR}/data/downloads:/downloads
- ${APP_DATA_DIR}/data/watch:/watch
ports:
- ${APP_PORT}:9091
- 51413:51413
- 51413:51413/udp
restart: unless-stopped
networks:
- tipi_main_network

35
apps/wg-easy/config.json Normal file
View file

@ -0,0 +1,35 @@
{
"name": "Wireguard",
"port": 8082,
"requirements": {
"ports": [51820]
},
"id": "wg-easy",
"description": "Access your homeserver from anywhere even on your mobile device. Wireguard-easy is a simple tool to configure and manage Wireguard VPN servers. It is written in Go and uses the official Wireguard client. You have to open and redirect port 51820 to your homeserver in order to connect.",
"short_desc": "VPN server for your homeserver",
"author": "WeeJeWel",
"source": "https://github.com/WeeJeWel/wg-easy/",
"image": "https://avatars.githubusercontent.com/u/13991055?s=200&v=4",
"form_fields": {
"host": {
"type": "fqdnip",
"label": "Your public IP address or domain name",
"required": true,
"env_variable": "WIREGUARD_HOST"
},
"password": {
"type": "password",
"label": "Password",
"max": 50,
"min": 3,
"required": true,
"env_variable": "WIREGUARD_PASSWORD"
},
"dns": {
"type": "ip",
"label": "Default DNS server",
"required": false,
"env_variable": "WIREGUARD_DNS"
}
}
}

View file

View file

@ -1,29 +1,35 @@
version: '3.7' version: "3.7"
services: services:
wg-easy: wg-easy:
container_name: wg-easy container_name: wg-easy
image: 'weejewel/wg-easy:latest' image: "meienberger/wg-easy:latest"
restart: unless-stopped restart: unless-stopped
volumes: # network_mode: "host"
- ${APP_DATA_DIR}:/etc/wireguard volumes:
ports: - ${APP_DATA_DIR}/data:/etc/wireguard
- 51820:51820 - /lib/modules:/lib/modules
- ${APP_WGEASY_PORT}:51821 ports:
environment: - 51822:51820/udp
WG_HOST: '${WIREGUARD_HOST}' - ${APP_PORT}:51821/tcp
PASSWORD: '${WIREGUARD_PASSWORD}' environment:
cap_add: WG_HOST: "${WIREGUARD_HOST}"
- NET_ADMIN PASSWORD: "${WIREGUARD_PASSWORD}"
- SYS_MODULE WG_ALLOWED_IPS: 0.0.0.0/0,::/0
sysctls: WG_PORT: 51822
- net.ipv4.conf.all.src_valid_mark=1 WG_DEFAULT_DNS: "${WIREGUARD_DNS:-8.8.8.8}"
- net.ipv4.ip_forward=1 # WG_FWMARK: 51820
networks: cap_add:
- tipi_main_network - NET_ADMIN
# labels: - SYS_MODULE
# traefik.enable: true sysctls:
# traefik.http.routers.wireguard.rule: Host(`wireguard.tipi.home`) - net.ipv4.conf.all.src_valid_mark=1
# traefik.http.routers.wireguard.service: wireguard - net.ipv4.ip_forward=1
# traefik.http.routers.wireguard.tls: true networks:
# traefik.http.routers.wireguard.entrypoints: websecure - tipi_main_network
# traefik.http.services.wireguard.loadbalancer.server.port: 51821 # labels:
# traefik.enable: true
# traefik.http.routers.wireguard.rule: Host(`wireguard.tipi.home`)
# traefik.http.routers.wireguard.service: wireguard
# traefik.http.routers.wireguard.tls: true
# traefik.http.routers.wireguard.entrypoints: websecure
# traefik.http.services.wireguard.loadbalancer.server.port: 51821

17
dashboard/.eslintrc.js Normal file
View file

@ -0,0 +1,17 @@
module.exports = {
extends: ['next/core-web-vitals', 'airbnb-typescript', 'eslint:recommended', 'plugin:import/typescript'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json',
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint', 'import'],
rules: {
'arrow-body-style': 0,
'no-restricted-exports': 0,
'max-len': [1, { code: 200 }],
'import/extensions': ['error', 'ignorePackages', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' }],
},
};

View file

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

6
dashboard/.prettierrc.js Normal file
View file

@ -0,0 +1,6 @@
module.exports = {
singleQuote: true,
semi: true,
trailingComma: "all",
printWidth: 200,
};

View file

@ -9,6 +9,9 @@ RUN yarn
COPY ./ ./ COPY ./ ./
ARG INTERNAL_IP_ARG
ENV INTERNAL_IP $INTERNAL_IP_ARG
RUN yarn build RUN yarn build
CMD ["yarn", "start"] CMD ["yarn", "start"]

12
dashboard/Dockerfile.dev Normal file
View file

@ -0,0 +1,12 @@
FROM node:latest
WORKDIR /app
COPY ./package.json ./
COPY ./yarn.lock ./
RUN yarn
COPY ./ ./
CMD ["yarn", "dev"]

View file

@ -1,6 +1,11 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
console.log(process.env);
const nextConfig = { const nextConfig = {
reactStrictMode: true, reactStrictMode: true,
} env: {
INTERNAL_IP: process.env.INTERNAL_IP,
},
};
module.exports = nextConfig module.exports = nextConfig;

View file

@ -12,20 +12,33 @@
"@chakra-ui/react": "^1.8.7", "@chakra-ui/react": "^1.8.7",
"@emotion/react": "^11", "@emotion/react": "^11",
"@emotion/styled": "^11", "@emotion/styled": "^11",
"axios": "^0.26.1",
"clsx": "^1.1.1",
"final-form": "^4.20.6",
"framer-motion": "^6", "framer-motion": "^6",
"immer": "^9.0.12",
"next": "12.1.4", "next": "12.1.4",
"react": "18.0.0", "react": "18.0.0",
"react-dom": "18.0.0", "react-dom": "18.0.0",
"react-final-form": "^6.5.9",
"react-icons": "^4.3.1",
"swr": "^1.3.0",
"systeminformation": "^5.11.9", "systeminformation": "^5.11.9",
"validator": "^13.7.0" "validator": "^13.7.0",
"zustand": "^3.7.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "17.0.23", "@types/node": "17.0.23",
"@types/react": "17.0.43", "@types/react": "17.0.43",
"@types/react-dom": "17.0.14", "@types/react-dom": "17.0.14",
"@types/validator": "^13.7.2", "@types/validator": "^13.7.2",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"autoprefixer": "^10.4.4",
"eslint": "8.12.0", "eslint": "8.12.0",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "12.1.4", "eslint-config-next": "12.1.4",
"postcss": "^8.4.12",
"tailwindcss": "^3.0.23",
"typescript": "4.6.3" "typescript": "4.6.3"
} }
} }

View file

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

BIN
dashboard/public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -1,4 +0,0 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,23 @@
import React from 'react';
import { FiPauseCircle, FiPlayCircle } from 'react-icons/fi';
import { AppStatus as TAppStatus } from '../../core/types';
const AppStatus: React.FC<{ status: TAppStatus }> = ({ status }) => {
if (status === 'running') {
return (
<>
<FiPlayCircle className="text-green-500 mr-1" size={20} />
<span className="text-gray-400 text-sm">Running</span>
</>
);
}
return (
<>
<FiPauseCircle className="text-red-500 mr-1" size={20} />
<span className="text-gray-400 text-sm">Stopped</span>
</>
);
};
export default AppStatus;

View file

@ -0,0 +1,30 @@
import { Box, SlideFade, Image } from '@chakra-ui/react';
import Link from 'next/link';
import React from 'react';
import { FiChevronRight } from 'react-icons/fi';
import { AppConfig } from '../../core/types';
import AppStatus from './AppStatus';
const AppTile: React.FC<{ app: AppConfig }> = ({ app }) => {
return (
<Link href={`/apps/${app.id}`} passHref>
<SlideFade in className="flex flex-1" offsetY="20px">
<Box minWidth={400} className="flex flex-1 bg-white drop-shadow-lg rounded-lg p-3 items-center cursor-pointer group hover:drop-shadow-md hover:bg-gray-100 transition-all">
<Image alt={`${app.name} logo`} className="rounded-md drop-shadow mr-3 group-hover:scale-105 transition-all" src={app.image} width={100} height={100} />
<div className="mr-3 flex-1">
<h3 className="font-bold text-xl">{app.name}</h3>
<span>{app.short_desc}</span>
{app.installed && (
<div className="flex mt-1">
<AppStatus status={app.status} />
</div>
)}
</div>
<FiChevronRight className="text-slate-300" size={30} />
</Box>
</SlideFade>
</Link>
);
};
export default AppTile;

View file

@ -0,0 +1,24 @@
import React from 'react';
import { Input } from '@chakra-ui/react';
import clsx from 'clsx';
interface IProps {
placeholder?: string;
error?: string;
type?: Parameters<typeof Input>[0]['type'];
label: string;
className?: string;
isInvalid?: boolean;
}
const FormInput: React.FC<IProps> = ({ placeholder, error, type, label, className, isInvalid, ...rest }) => {
return (
<div className={clsx('transition-all', className)}>
<label>{label}</label>
<Input type={type} placeholder={placeholder} isInvalid={isInvalid} {...rest} />
{isInvalid && <span className="text-red-500 text-sm">{error}</span>}
</div>
);
};
export default FormInput;

View file

@ -0,0 +1,28 @@
import validator from 'validator';
import { AppConfig, FieldTypes } from '../../core/types';
export const validateAppConfig = (values: Record<string, string>, fields: (AppConfig['form_fields'][0] & { id: string })[]) => {
const errors: any = {};
fields.forEach((field) => {
if (field.required && !values[field.id]) {
errors[field.id] = 'Field required';
} else if (values[field.id] && field.min && values[field.id].length < field.min) {
errors[field.id] = `Field must be at least ${field.min} characters long`;
} else if (values[field.id] && field.max && values[field.id].length > field.max) {
errors[field.id] = `Field must be at most ${field.max} characters long`;
} else if (values[field.id] && field.type === FieldTypes.number && !validator.isNumeric(values[field.id])) {
errors[field.id] = 'Field must be a number';
} else if (values[field.id] && field.type === FieldTypes.email && !validator.isEmail(values[field.id])) {
errors[field.id] = 'Field must be a valid email';
} else if (values[field.id] && field.type === FieldTypes.fqdn && !validator.isFQDN(values[field.id] || '')) {
errors[field.id] = 'Field must be a valid domain';
} else if (values[field.id] && field.type === FieldTypes.ip && !validator.isIP(values[field.id])) {
errors[field.id] = 'Field must be a valid IP address';
} else if (values[field.id] && field.type === FieldTypes.fqdnip && !validator.isFQDN(values[field.id] || '') && !validator.isIP(values[field.id])) {
errors[field.id] = 'Field must be a valid domain or IP address';
}
});
return errors;
};

View file

@ -1,27 +1,22 @@
import React from "react"; import React from 'react';
import Img from "next/image"; import Link from 'next/link';
import Link from "next/link"; import { Flex } from '@chakra-ui/react';
import { Button, Flex, useBreakpointValue } from "@chakra-ui/react"; import { FiMenu } from 'react-icons/fi';
interface IProps { interface IProps {
onClickMenu: () => void; onClickMenu: () => void;
} }
const Header: React.FC<IProps> = ({ onClickMenu }) => { const Header: React.FC<IProps> = ({ onClickMenu }) => {
const buttonVisibility = useBreakpointValue<"visible" | "hidden">({
base: "visible",
md: "hidden",
});
return ( return (
<header> <header style={{ width: '100%' }} className="flex">
<Flex alignItems="center" bg="tomato" paddingLeft={5} paddingRight={5}> <Flex className="items-center bg-gray-700 drop-shadow-md px-5 flex-1">
<Flex position="absolute" visibility={buttonVisibility || "visible"}> <div onClick={onClickMenu} className="visible md:invisible absolute cursor-pointer py-2">
<Button onClick={onClickMenu}>O</Button> <FiMenu color="white" />
</Flex> </div>
<Flex justifyContent="center" flex="1"> <Flex justifyContent="center" flex="1">
<Link href="/" passHref> <Link href="/" passHref>
<Img src="/logo.svg" alt="Tipi" width={100} height={60} /> <img src="/logo.png" alt="Tipi" width={230} height={60} />
</Link> </Link>
</Flex> </Flex>
</Flex> </Flex>

View file

@ -1,30 +1,60 @@
import { import { Flex, useDisclosure, Spinner, Breadcrumb, BreadcrumbItem } from '@chakra-ui/react';
Button, import Link from 'next/link';
Flex, import React from 'react';
useBreakpointValue, import { FiChevronRight } from 'react-icons/fi';
useDisclosure, import Header from './Header';
} from "@chakra-ui/react"; import Menu from './Menu';
import React from "react"; import MenuDrawer from './MenuDrawer';
import Header from "./Header";
import Menu from "./Menu";
import MenuDrawer from "./MenuDrawer";
const Layout: React.FC = ({ children }) => { interface IProps {
const menuWidth = useBreakpointValue({ base: 0, md: 200 }); loading?: boolean;
breadcrumbs?: { name: string; href: string; current?: boolean }[];
}
const Layout: React.FC<IProps> = ({ children, loading, breadcrumbs }) => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const renderContent = () => {
if (loading) {
return (
<Flex className="justify-center flex-1">
<Spinner />
</Flex>
);
}
return children;
};
const renderBreadcrumbs = () => {
return (
<Breadcrumb spacing="8px" separator={<FiChevronRight color="gray.500" />}>
{breadcrumbs?.map((breadcrumb, index) => {
return (
<BreadcrumbItem className="hover:underline" isCurrentPage={breadcrumb.current} key={index}>
<Link href={breadcrumb.href}>{breadcrumb.name}</Link>
</BreadcrumbItem>
);
})}
</Breadcrumb>
);
};
return ( return (
<Flex height="100vh" bg="green.500" direction="column"> <Flex height="100vh" className="drop-shadow-md border-r-8" direction="column">
<MenuDrawer isOpen={isOpen} onClose={onClose}> <MenuDrawer isOpen={isOpen} onClose={onClose}>
<Menu /> <Menu />
</MenuDrawer> </MenuDrawer>
<Header onClickMenu={onOpen} /> <Header onClickMenu={onOpen} />
<Flex flex="1"> <Flex flex="1">
<Flex width={menuWidth} bg="blue.500"> <Flex className="invisible md:visible w-0 md:w-56">
<Menu /> <Menu />
</Flex> </Flex>
<Flex flex="1" padding={5} bg="yellow.300"> <Flex className="bg-slate-200 flex flex-1 p-5">
{children} <div className="flex-1 flex flex-col">
{renderBreadcrumbs()}
<div className="flex-1 ">{renderContent()}</div>
</div>
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>

View file

@ -1,11 +1,40 @@
import React from "react"; import { AiOutlineDashboard, AiOutlineSetting, AiOutlineAppstore } from 'react-icons/ai';
import { Divider, List, ListItem } from '@chakra-ui/react';
import React from 'react';
import Link from 'next/link';
import clsx from 'clsx';
import { useRouter } from 'next/router';
import { IconType } from 'react-icons';
const SideMenu: React.FC = () => {
const router = useRouter();
const path = router.pathname.split('/')[1];
const renderMenuItem = (title: string, name: string, Icon: IconType) => {
const selected = path === name;
return (
<Link href={`/${name}`} passHref>
<div className={clsx('mx-3 rounded-lg p-3 transition-colors', { 'bg-slate-200 drop-shadow-sm': selected })}>
<ListItem className={'flex items-center cursor-pointer hover:font-bold'}>
<Icon size={20} className="mr-3" />
<p className={clsx({ 'font-bold': selected })}>{title}</p>
</ListItem>
</div>
</Link>
);
};
const Menu: React.FC = () => {
return ( return (
<div> <List spacing={3} className="pt-5 flex-1 bg-white md:border-r-2">
<h1>Menu</h1> {renderMenuItem('Dashboard', '', AiOutlineDashboard)}
</div> <Divider />
{renderMenuItem('Apps', 'apps', AiOutlineAppstore)}
<Divider />
{renderMenuItem('Settings', 'settings', AiOutlineSetting)}
</List>
); );
}; };
export default Menu; export default SideMenu;

View file

@ -1,13 +1,5 @@
import { import { Drawer, DrawerBody, DrawerCloseButton, DrawerContent, DrawerFooter, DrawerHeader, DrawerOverlay } from '@chakra-ui/react';
Drawer, import React from 'react';
DrawerBody,
DrawerCloseButton,
DrawerContent,
DrawerFooter,
DrawerHeader,
DrawerOverlay,
} from "@chakra-ui/react";
import React from "react";
interface IProps { interface IProps {
isOpen: boolean; isOpen: boolean;
@ -16,11 +8,11 @@ interface IProps {
const MenuDrawer: React.FC<IProps> = ({ children, isOpen, onClose }) => { const MenuDrawer: React.FC<IProps> = ({ children, isOpen, onClose }) => {
return ( return (
<Drawer isOpen={isOpen} placement="left" onClose={onClose}> <Drawer size="xs" isOpen={isOpen} placement="left" onClose={onClose}>
<DrawerOverlay /> <DrawerOverlay />
<DrawerContent> <DrawerContent>
<DrawerCloseButton /> <DrawerCloseButton />
<DrawerHeader>Create your account</DrawerHeader> <DrawerHeader>My Tipi</DrawerHeader>
<DrawerBody>{children}</DrawerBody> <DrawerBody>{children}</DrawerBody>
<DrawerFooter> <DrawerFooter>
<div>Github</div> <div>Github</div>

View file

@ -1 +1 @@
export { default } from "./Layout"; export { default } from './Layout';

View file

@ -1,4 +1,4 @@
import validator from "validator"; import validator from 'validator';
interface IFormField { interface IFormField {
name: string; name: string;
@ -20,81 +20,79 @@ interface IAppConfig {
} }
const APP_ANONADDY: IAppConfig = { const APP_ANONADDY: IAppConfig = {
id: "anonaddy", id: 'anonaddy',
name: "Anonaddy", name: 'Anonaddy',
description: "Create Unlimited Email Aliases For Free", description: 'Create Unlimited Email Aliases For Free',
url: "https://anonaddy.com/", url: 'https://anonaddy.com/',
color: "#00a8ff", color: '#00a8ff',
logo: "https://anonaddy.com/favicon.ico", logo: 'https://anonaddy.com/favicon.ico',
install_form: { install_form: {
fields: [ fields: [
{ {
name: "API Key", name: 'API Key',
type: "text", type: 'text',
placeholder: "API Key", placeholder: 'API Key',
required: true, required: true,
validate: (value: string) => validator.isBase64(value), validate: (value: string) => validator.isBase64(value),
}, },
{ {
name: "Return Path", name: 'Return Path',
type: "text", type: 'text',
description: "The email address that bounces will be sent to", description: 'The email address that bounces will be sent to',
placeholder: "Return Path", placeholder: 'Return Path',
required: false, required: false,
validate: (value: string) => validator.isEmail(value), validate: (value: string) => validator.isEmail(value),
}, },
{ {
name: "Admin Username", name: 'Admin Username',
type: "text", type: 'text',
description: "The username of the admin user", description: 'The username of the admin user',
placeholder: "Admin Username", placeholder: 'Admin Username',
required: true, required: true,
}, },
{ {
name: "Enable Registration", name: 'Enable Registration',
type: "boolean", type: 'boolean',
description: "Allow users to register", description: 'Allow users to register',
placeholder: "Enable Registration", placeholder: 'Enable Registration',
required: false, required: false,
}, },
{ {
name: "Domain", name: 'Domain',
type: "text", type: 'text',
description: "The domain that will be used for the email address", description: 'The domain that will be used for the email address',
placeholder: "Domain", placeholder: 'Domain',
required: true, required: true,
validate: (value: string) => validator.isFQDN(value), validate: (value: string) => validator.isFQDN(value),
}, },
{ {
name: "Hostname", name: 'Hostname',
type: "text", type: 'text',
description: "The hostname that will be used for the email address", description: 'The hostname that will be used for the email address',
placeholder: "Hostname", placeholder: 'Hostname',
required: true, required: true,
validate: (value: string) => validator.isFQDN(value), validate: (value: string) => validator.isFQDN(value),
}, },
{ {
name: "Secret", name: 'Secret',
type: "text", type: 'text',
description: "The secret that will be used for the email address", description: 'The secret that will be used for the email address',
placeholder: "Secret", placeholder: 'Secret',
required: true, required: true,
}, },
{ {
name: "From Name", name: 'From Name',
type: "text", type: 'text',
description: "The name that will be used for the email address", description: 'The name that will be used for the email address',
placeholder: "From Name", placeholder: 'From Name',
required: true, required: true,
validate: (value: string) => validate: (value: string) => validator.isLength(value, { min: 1, max: 64 }),
validator.isLength(value, { min: 1, max: 64 }),
}, },
{ {
name: "From Address", name: 'From Address',
type: "text", type: 'text',
description: description: 'The email address that will be used for the email address',
"The email address that will be used for the email address", placeholder: 'From Address',
placeholder: "From Address",
required: true, required: true,
validate: (value: string) => validator.isEmail(value), validate: (value: string) => validator.isEmail(value),
}, },

33
dashboard/src/core/api.ts Normal file
View file

@ -0,0 +1,33 @@
import axios, { Method } from 'axios';
export const BASE_URL = 'http://192.168.2.132:3001';
console.log(process.env);
interface IFetchParams {
endpoint: string;
method?: Method;
params?: JSON;
data?: Record<string, unknown>;
}
const api = async <T = unknown>(fetchParams: IFetchParams): Promise<T> => {
const { endpoint, method = 'GET', params, data } = fetchParams;
const response = await axios.request<T & { error?: string }>({
method,
params,
data,
url: `${BASE_URL}${endpoint}`,
});
if (response.data.error) {
throw new Error(response.data.error);
}
if (response.data) return response.data;
throw new Error(`Network request error. status : ${response.status}`);
};
export default { fetch: api };

View file

@ -0,0 +1,9 @@
import { BareFetcher } from 'swr';
import axios from 'axios';
import { BASE_URL } from './api';
const fetcher: BareFetcher<any> = (url: string) => {
return axios.get(url, { baseURL: BASE_URL }).then((res) => res.data);
};
export default fetcher;

View file

@ -0,0 +1,51 @@
export enum FieldTypes {
text = 'text',
password = 'password',
email = 'email',
number = 'number',
fqdn = 'fqdn',
ip = 'ip',
fqdnip = 'fqdnip',
}
interface FormField {
type: FieldTypes;
label: string;
max?: number;
min?: number;
required?: boolean;
env_variable: string;
}
export interface AppConfig {
id: string;
port: number;
requirements?: {
ports?: number[];
};
name: string;
description: string;
version: string;
image: string;
form_fields: Record<string, FormField>;
short_desc: string;
author: string;
source: string;
installed: boolean;
status: AppStatus;
}
export enum RequestStatus {
SUCCESS = 'SUCCESS',
ERROR = 'ERROR',
LOADING = 'LOADING',
}
export enum AppStatus {
RUNNING = 'running',
STOPPED = 'stopped',
INSTALLING = 'installing',
UNINSTALLING = 'uninstalling',
STOPPING = 'stopping',
STARTING = 'starting',
}

View file

@ -0,0 +1,70 @@
import { Button } from '@chakra-ui/react';
import React from 'react';
import { FiExternalLink, FiPause, FiPlay, FiSettings, FiTrash2 } from 'react-icons/fi';
import { AppConfig, AppStatus } from '../../../core/types';
interface IProps {
app: AppConfig;
onInstall: () => void;
onUninstall: () => void;
onStart: () => void;
onStop: () => void;
onOpen: () => void;
onUpdate: () => void;
}
const AppActions: React.FC<IProps> = ({ app, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate }) => {
const hasSettings = Object.keys(app.form_fields).length > 0;
if (app?.installed && app.status === AppStatus.STOPPED) {
return (
<div className="flex flex-wrap justify-center">
<Button onClick={onStart} width={160} colorScheme="green" className="mt-3 mr-2">
Start
<FiPlay className="ml-1" />
</Button>
<Button onClick={onUninstall} width={160} colorScheme="gray" className="mt-3 mr-2">
Remove
<FiTrash2 className="ml-1" />
</Button>
{hasSettings && (
<Button onClick={onUpdate} width={160} className="mt-3 mr-2">
Settings
<FiSettings className="ml-1" />
</Button>
)}
</div>
);
} else if (app?.installed && app.status === AppStatus.RUNNING) {
return (
<div>
<Button onClick={onOpen} width={160} colorScheme="gray" className="mt-3 mr-2">
Open
<FiExternalLink className="ml-1" />
</Button>
<Button onClick={onStop} width={160} colorScheme="red" className="mt-3">
Stop
<FiPause className="ml-2" />
</Button>
</div>
);
} else if (app.status === AppStatus.INSTALLING || app.status === AppStatus.UNINSTALLING || app.status === AppStatus.STARTING || app.status === AppStatus.STOPPING) {
return (
<div className="flex items-center flex-col md:flex-row">
<Button isLoading onClick={() => null} width={160} colorScheme="green" className="mt-3">
Install
<FiPlay className="ml-1" />
</Button>
<span className="text-gray-500 text-sm ml-2 mt-3">{`App is ${app.status} please wait and don't refresh page...`}</span>
</div>
);
}
return (
<Button onClick={onInstall} width={160} colorScheme="green" className="mt-3">
Install
</Button>
);
};
export default AppActions;

View file

@ -0,0 +1,46 @@
import { Button } from '@chakra-ui/react';
import React from 'react';
import { Form, Field } from 'react-final-form';
import FormInput from '../../../components/Form/FormInput';
import { validateAppConfig } from '../../../components/Form/validators';
import { AppConfig } from '../../../core/types';
import { objectKeys } from '../../../utils/typescript';
interface IProps {
formFields: AppConfig['form_fields'];
onSubmit: (values: Record<string, unknown>) => void;
initalValues?: Record<string, string>;
}
const InstallForm: React.FC<IProps> = ({ formFields, onSubmit, initalValues }) => {
const fields = objectKeys(formFields).map((key) => ({ ...formFields[key], id: key }));
const renderField = (field: typeof fields[0]) => {
return (
<Field
key={field.id}
name={field.id}
render={({ input, meta }) => <FormInput className="mb-3" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} label={field.label} {...input} />}
/>
);
};
return (
<Form<Record<string, string>>
initialValues={initalValues}
onSubmit={onSubmit}
validateOnBlur={true}
validate={(values) => validateAppConfig(values, fields)}
render={({ handleSubmit, validating, submitting }) => (
<form className="flex flex-col" onSubmit={handleSubmit}>
{fields.map(renderField)}
<Button isLoading={validating || submitting} className="self-end mb-2" colorScheme="green" type="submit">
{initalValues ? 'Update' : 'Install'}
</Button>
</form>
)}
/>
);
};
export default InstallForm;

View file

@ -0,0 +1,28 @@
import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import React from 'react';
import { AppConfig } from '../../../core/types';
import InstallForm from './InstallForm';
interface IProps {
app: AppConfig;
isOpen: boolean;
onClose: () => void;
onSubmit: (values: Record<string, any>) => void;
}
const InstallModal: React.FC<IProps> = ({ app, isOpen, onClose, onSubmit }) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Install {app.name}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<InstallForm onSubmit={onSubmit} formFields={app.form_fields} />
</ModalBody>
</ModalContent>
</Modal>
);
};
export default InstallModal;

View file

@ -0,0 +1,30 @@
import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import React from 'react';
import { AppConfig } from '../../../core/types';
interface IProps {
app: AppConfig;
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
}
const StopModal: React.FC<IProps> = ({ app, isOpen, onClose, onConfirm }) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Stop {app.name} ?</ModalHeader>
<ModalCloseButton />
<ModalBody>All the data will be retained.</ModalBody>
<ModalFooter>
<Button onClick={onConfirm} colorScheme="red">
Stop
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default StopModal;

View file

@ -0,0 +1,30 @@
import { Button, Modal, ModalBody, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import React from 'react';
import { AppConfig } from '../../../core/types';
interface IProps {
app: AppConfig;
isOpen: boolean;
onClose: () => void;
onConfirm: () => void;
}
const UninstallModal: React.FC<IProps> = ({ app, isOpen, onClose, onConfirm }) => {
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Uninstall {app.name} ?</ModalHeader>
<ModalCloseButton />
<ModalBody>All data for this app will be lost.</ModalBody>
<ModalFooter>
<Button onClick={onConfirm} colorScheme="red">
Uninstall
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};
export default UninstallModal;

View file

@ -0,0 +1,36 @@
import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
import React, { useEffect } from 'react';
import useSWR from 'swr';
import fetcher from '../../../core/fetcher';
import { AppConfig } from '../../../core/types';
import InstallForm from './InstallForm';
interface IProps {
app: AppConfig;
isOpen: boolean;
onClose: () => void;
onSubmit: (values: Record<string, any>) => void;
}
const UpdateModal: React.FC<IProps> = ({ app, isOpen, onClose, onSubmit }) => {
const { data, mutate } = useSWR<Record<string, string>>(`/apps/form/${app.id}`, fetcher, { refreshInterval: 10 });
useEffect(() => {
mutate({}, true);
}, [isOpen, mutate]);
return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Update {app.name} config</ModalHeader>
<ModalCloseButton />
<ModalBody>
<InstallForm onSubmit={onSubmit} formFields={app.form_fields} initalValues={data} />
</ModalBody>
</ModalContent>
</Modal>
);
};
export default UpdateModal;

View file

@ -0,0 +1,136 @@
import { SlideFade, Image, VStack, Flex, Divider, useDisclosure, useToast } from '@chakra-ui/react';
import React from 'react';
import { FiExternalLink } from 'react-icons/fi';
import { AppConfig } from '../../../core/types';
import { useAppsStore } from '../../../state/appsStore';
import { useNetworkStore } from '../../../state/networkStore';
import AppActions from '../components/AppActions';
import InstallModal from '../components/InstallModal';
import StopModal from '../components/StopModal';
import UninstallModal from '../components/UninstallModal';
import UpdateModal from '../components/UpdateModal';
interface IProps {
app: AppConfig;
}
const AppDetails: React.FC<IProps> = ({ app }) => {
const toast = useToast();
const installDisclosure = useDisclosure();
const uninstallDisclosure = useDisclosure();
const stopDisclosure = useDisclosure();
const updateDisclosure = useDisclosure();
const { internalIp } = useNetworkStore();
const { install, update, uninstall, stop, start, fetchApp } = useAppsStore();
const handleError = (error: unknown) => {
if (error instanceof Error) {
toast({
title: 'Error',
description: error.message,
status: 'error',
position: 'top',
isClosable: true,
});
fetchApp(app.id);
}
};
const handleInstallSubmit = async (values: Record<string, any>) => {
installDisclosure.onClose();
try {
await install(app.id, values);
} catch (error) {
handleError(error);
}
};
const handleUnistallSubmit = async () => {
uninstallDisclosure.onClose();
try {
await uninstall(app.id);
} catch (error) {
handleError(error);
}
};
const handleStopSubmit = async () => {
stopDisclosure.onClose();
try {
await stop(app.id);
} catch (error) {
handleError(error);
}
};
const handleStartSubmit = async () => {
try {
await start(app.id);
} catch (e: unknown) {
handleError(e);
}
};
const handleUpdateSubmit = async (values: Record<string, any>) => {
try {
await update(app.id, values);
toast({
title: 'Success',
description: 'App config updated successfully',
position: 'top',
status: 'success',
});
updateDisclosure.onClose();
} catch (error) {
handleError(error);
}
};
const handleOpen = () => {
window.open(`http://${internalIp}:${app.port}`, '_blank');
};
return (
<SlideFade in className="flex flex-1" offsetY="20px">
<div className="flex flex-1 bg-white p-4 mt-3 rounded-lg drop-shadow-xl flex-col">
<Flex className="flex-col md:flex-row">
<Image src={app?.image} height={180} width={180} className="rounded-xl self-center sm:self-auto" alt={app.name} />
<VStack align="flex-start" justify="space-between" className="ml-0 md:ml-4">
<div className="mt-3 items-center self-center flex flex-col sm:items-start sm:self-start md:mt-0">
<h1 className="font-bold text-2xl">{app?.name}</h1>
<h2 className="text-center md:text-left">{app?.short_desc}</h2>
{app.source && (
<a target="_blank" rel="noreferrer" className="text-blue-500 text-xs" href={app?.source}>
<Flex className="mt-2 items-center">
<FiExternalLink className="ml-1" />
</Flex>
</a>
)}
<p className="text-xs text-gray-600">By {app?.author}</p>
</div>
<div className="flex justify-center sm:absolute md:static top-0 right-5 self-center sm:self-auto">
<AppActions
onUpdate={updateDisclosure.onOpen}
onOpen={handleOpen}
onStart={handleStartSubmit}
onStop={stopDisclosure.onOpen}
onUninstall={uninstallDisclosure.onOpen}
onInstall={installDisclosure.onOpen}
app={app}
/>
</div>
</VStack>
</Flex>
<Divider className="mt-5" />
<p className="mt-3">{app?.description}</p>
<InstallModal onSubmit={handleInstallSubmit} isOpen={installDisclosure.isOpen} onClose={installDisclosure.onClose} app={app} />
<UninstallModal onConfirm={handleUnistallSubmit} isOpen={uninstallDisclosure.isOpen} onClose={uninstallDisclosure.onClose} app={app} />
<StopModal onConfirm={handleStopSubmit} isOpen={stopDisclosure.isOpen} onClose={stopDisclosure.onClose} app={app} />
<UpdateModal onSubmit={handleUpdateSubmit} isOpen={updateDisclosure.isOpen} onClose={updateDisclosure.onClose} app={app} />
</div>
</SlideFade>
);
};
export default AppDetails;

View file

@ -1,8 +1,16 @@
import { ChakraProvider } from "@chakra-ui/react"; import { ChakraProvider } from '@chakra-ui/react';
import "../styles/globals.css"; import '../styles/globals.css';
import type { AppProps } from "next/app"; import type { AppProps } from 'next/app';
import { useEffect } from 'react';
import { useNetworkStore } from '../state/networkStore';
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
const { fetchInternalIp } = useNetworkStore();
useEffect(() => {
fetchInternalIp();
}, [fetchInternalIp]);
return ( return (
<ChakraProvider> <ChakraProvider>
<Component {...pageProps} /> <Component {...pageProps} />

View file

@ -1,6 +1,6 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from 'next';
import si from "systeminformation"; import si from 'systeminformation';
type Data = Awaited<ReturnType<typeof si.currentLoad>>; type Data = Awaited<ReturnType<typeof si.currentLoad>>;

View file

@ -1,6 +1,6 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from 'next';
import si from "systeminformation"; import si from 'systeminformation';
type Data = Awaited<ReturnType<typeof si.fsSize>>; type Data = Awaited<ReturnType<typeof si.fsSize>>;

View file

@ -1,6 +1,6 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next"; import type { NextApiRequest, NextApiResponse } from 'next';
import si from "systeminformation"; import si from 'systeminformation';
type Data = Awaited<ReturnType<typeof si.mem>>; type Data = Awaited<ReturnType<typeof si.mem>>;

View file

@ -0,0 +1,39 @@
import type { NextPage } from 'next';
import { useEffect } from 'react';
import Layout from '../../components/Layout';
import { useAppsStore } from '../../state/appsStore';
import AppDetails from '../../modules/Apps/containers/AppDetails';
interface IProps {
appId: string;
}
const AppDetailsPage: NextPage<IProps> = ({ appId }) => {
const { fetchApp, getApp } = useAppsStore((state) => state);
const app = getApp(appId);
useEffect(() => {
fetchApp(appId);
}, [appId, fetchApp]);
const breadcrumb = [
{ name: 'Apps', href: '/apps' },
{ name: app?.name || '', href: `/apps/${appId}`, current: true },
];
return (
<Layout breadcrumbs={breadcrumb} loading={!app}>
{app && <AppDetails app={app} />}
</Layout>
);
};
AppDetailsPage.getInitialProps = async (ctx) => {
const { query } = ctx;
const appId = query.id as string;
return { appId };
};
export default AppDetailsPage;

View file

@ -0,0 +1,39 @@
import React, { useEffect } from 'react';
import { Flex, SimpleGrid } from '@chakra-ui/react';
import type { NextPage } from 'next';
import Layout from '../../components/Layout';
import { RequestStatus } from '../../core/types';
import { useAppsStore } from '../../state/appsStore';
import AppTile from '../../components/AppTile';
const Apps: NextPage = () => {
const { available, installed, fetch, status } = useAppsStore((state) => state);
useEffect(() => {
fetch();
}, [fetch]);
const installedCount: number = installed().length || 0;
const loading = status === RequestStatus.LOADING && !installed && !available;
return (
<Layout loading={loading}>
<Flex className="flex-col">
{installedCount > 0 && <h1 className="font-bold text-2xl mb-3">Your Apps ({installedCount})</h1>}
<SimpleGrid minChildWidth="400px" spacing="20px">
{installed().map((app) => (
<AppTile key={app.name} app={app} />
))}
</SimpleGrid>
{available().length && <h1 className="font-bold text-2xl mb-3 mt-3">Available Apps</h1>}
<SimpleGrid minChildWidth="400px" spacing="20px">
{available().map((app) => (
<AppTile key={app.name} app={app} />
))}
</SimpleGrid>
</Flex>
</Layout>
);
};
export default Apps;

View file

@ -1,5 +1,5 @@
import type { NextPage } from "next"; import type { NextPage } from 'next';
import Layout from "../components/Layout"; import Layout from '../components/Layout';
const Home: NextPage = () => { const Home: NextPage = () => {
return ( return (

View file

@ -0,0 +1,12 @@
import type { NextPage } from 'next';
import Layout from '../components/Layout';
const Settings: NextPage = () => {
return (
<Layout>
<div>Settings</div>
</Layout>
);
};
export default Settings;

View file

@ -0,0 +1,127 @@
import produce from 'immer';
import create, { GetState, SetState } from 'zustand';
import api from '../core/api';
import { AppConfig, AppStatus, RequestStatus } from '../core/types';
type AppsStore = {
apps: AppConfig[];
status: RequestStatus;
installed: () => AppConfig[];
available: () => AppConfig[];
fetch: () => void;
getApp: (id: string) => AppConfig | undefined;
fetchApp: (id: string) => void;
install: (id: string, form: Record<string, string>) => Promise<void>;
update: (id: string, form: Record<string, string>) => Promise<void>;
uninstall: (id: string) => Promise<void>;
stop: (id: string) => Promise<void>;
start: (id: string) => Promise<void>;
};
type Set = SetState<AppsStore>;
type Get = GetState<AppsStore>;
const sortApps = (apps: AppConfig[]) => apps.sort((a, b) => a.name.localeCompare(b.name));
const setAppStatus = (appId: string, status: AppStatus, set: Set) => {
set((state) => {
return produce(state, (draft) => {
const app = draft.apps.find((a) => a.id === appId);
if (app) app.status = status;
});
});
};
const installed = (get: Get) => {
const i = get().apps.filter((app) => app.installed);
return i;
};
/**
* Fetch one app and add it to the list of apps.
* @param appId
* @param set
*/
const fetchApp = async (appId: string, set: Set) => {
const response = await api.fetch<AppConfig>({
endpoint: `/apps/info/${appId}`,
method: 'get',
});
set((state) => {
const apps = state.apps.filter((app) => app.id !== appId);
apps.push(response);
return { ...state, apps: sortApps(apps) };
});
};
export const useAppsStore = create<AppsStore>((set, get) => ({
apps: [],
status: RequestStatus.LOADING,
installed: () => installed(get),
available: () => {
return get().apps.filter((app) => !app.installed);
},
fetchApp: async (appId: string) => fetchApp(appId, set),
fetch: async () => {
set({ status: RequestStatus.LOADING });
const response = await api.fetch<AppConfig[]>({
endpoint: '/apps/list',
method: 'get',
});
set({ apps: sortApps(response), status: RequestStatus.SUCCESS });
},
getApp: (appId: string) => {
return get().apps.find((app) => app.id === appId);
},
install: async (appId: string, form?: Record<string, string>) => {
setAppStatus(appId, AppStatus.INSTALLING, set);
await api.fetch({
endpoint: `/apps/install/${appId}`,
method: 'POST',
data: { form },
});
await get().fetchApp(appId);
},
update: async (appId: string, form?: Record<string, string>) => {
await api.fetch({
endpoint: `/apps/update/${appId}`,
method: 'POST',
data: { form },
});
await get().fetchApp(appId);
},
uninstall: async (appId: string) => {
setAppStatus(appId, AppStatus.UNINSTALLING, set);
await api.fetch({
endpoint: `/apps/uninstall/${appId}`,
});
await get().fetchApp(appId);
},
stop: async (appId: string) => {
setAppStatus(appId, AppStatus.STOPPING, set);
await api.fetch({
endpoint: `/apps/stop/${appId}`,
});
await get().fetchApp(appId);
},
start: async (appId: string) => {
setAppStatus(appId, AppStatus.STARTING, set);
await api.fetch({
endpoint: `/apps/start/${appId}`,
});
await get().fetchApp(appId);
},
}));

View file

@ -0,0 +1,19 @@
import create from 'zustand';
import api from '../core/api';
type AppsStore = {
internalIp: string;
fetchInternalIp: () => void;
};
export const useNetworkStore = create<AppsStore>((set) => ({
internalIp: '',
fetchInternalIp: async () => {
const response = await api.fetch<string>({
endpoint: '/network/internal-ip',
method: 'get',
});
set({ internalIp: response });
},
}));

View file

@ -0,0 +1,12 @@
import create from 'zustand';
type UIStore = {
menuItem: string;
};
export const useUIStore = create<UIStore>((set) => ({
menuItem: 'dashboard',
setMenuItem: (menuItem: string) => {
set({ menuItem: menuItem });
},
}));

View file

@ -1,6 +1,11 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html, html,
body { body {
padding: 0; padding: 0;
overflow-x: hidden;
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;

View file

@ -0,0 +1,3 @@
const objectKeys = <T>(obj: T): (keyof T)[] => Object.keys(obj) as (keyof T)[];
export { objectKeys };

View file

@ -0,0 +1,7 @@
module.exports = {
content: ['./src/pages/**/*.{ts,tsx}', './src/components/**/*.{ts,tsx}', './src/modules/**/*.{ts,tsx}'],
theme: {
extend: {},
},
plugins: [],
};

View file

@ -57,6 +57,13 @@
dependencies: dependencies:
regenerator-runtime "^0.13.4" regenerator-runtime "^0.13.4"
"@babel/runtime@^7.10.0", "@babel/runtime@^7.15.4":
version "7.17.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72"
integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==
dependencies:
regenerator-runtime "^0.13.4"
"@babel/types@^7.16.7": "@babel/types@^7.16.7":
version "7.17.0" version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
@ -892,6 +899,11 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64"
integrity sha512-ZK5v4bJwgXldAUA8r3q9YKfCwOqoHTK/ZqRjSeRXQrBXWouoPnS4MQtgC4AXGiiBuUu5wxrRgTlv0ktmM4P1Aw== integrity sha512-ZK5v4bJwgXldAUA8r3q9YKfCwOqoHTK/ZqRjSeRXQrBXWouoPnS4MQtgC4AXGiiBuUu5wxrRgTlv0ktmM4P1Aw==
"@types/json-schema@^7.0.9":
version "7.0.11"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/json5@^0.0.29": "@types/json5@^0.0.29":
version "0.0.29" version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@ -955,6 +967,21 @@
resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52"
integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI= integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI=
"@typescript-eslint/eslint-plugin@^5.18.0":
version "5.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.18.0.tgz#950df411cec65f90d75d6320a03b2c98f6c3af7d"
integrity sha512-tzrmdGMJI/uii9/V6lurMo4/o+dMTKDH82LkNjhJ3adCW22YQydoRs5MwTiqxGF9CSYxPxQ7EYb4jLNlIs+E+A==
dependencies:
"@typescript-eslint/scope-manager" "5.18.0"
"@typescript-eslint/type-utils" "5.18.0"
"@typescript-eslint/utils" "5.18.0"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
regexpp "^3.2.0"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/parser@5.10.1": "@typescript-eslint/parser@5.10.1":
version "5.10.1" version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.1.tgz#4ce9633cc33fc70bc13786cb793c1a76fe5ad6bd" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.1.tgz#4ce9633cc33fc70bc13786cb793c1a76fe5ad6bd"
@ -973,11 +1000,33 @@
"@typescript-eslint/types" "5.10.1" "@typescript-eslint/types" "5.10.1"
"@typescript-eslint/visitor-keys" "5.10.1" "@typescript-eslint/visitor-keys" "5.10.1"
"@typescript-eslint/scope-manager@5.18.0":
version "5.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.18.0.tgz#a7d7b49b973ba8cebf2a3710eefd457ef2fb5505"
integrity sha512-C0CZML6NyRDj+ZbMqh9FnPscg2PrzSaVQg3IpTmpe0NURMVBXlghGZgMYqBw07YW73i0MCqSDqv2SbywnCS8jQ==
dependencies:
"@typescript-eslint/types" "5.18.0"
"@typescript-eslint/visitor-keys" "5.18.0"
"@typescript-eslint/type-utils@5.18.0":
version "5.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.18.0.tgz#62dbfc8478abf36ba94a90ddf10be3cc8e471c74"
integrity sha512-vcn9/6J5D6jtHxpEJrgK8FhaM8r6J1/ZiNu70ZUJN554Y3D9t3iovi6u7JF8l/e7FcBIxeuTEidZDR70UuCIfA==
dependencies:
"@typescript-eslint/utils" "5.18.0"
debug "^4.3.2"
tsutils "^3.21.0"
"@typescript-eslint/types@5.10.1": "@typescript-eslint/types@5.10.1":
version "5.10.1" version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.1.tgz#dca9bd4cb8c067fc85304a31f38ec4766ba2d1ea" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.1.tgz#dca9bd4cb8c067fc85304a31f38ec4766ba2d1ea"
integrity sha512-ZvxQ2QMy49bIIBpTqFiOenucqUyjTQ0WNLhBM6X1fh1NNlYAC6Kxsx8bRTY3jdYsYg44a0Z/uEgQkohbR0H87Q== integrity sha512-ZvxQ2QMy49bIIBpTqFiOenucqUyjTQ0WNLhBM6X1fh1NNlYAC6Kxsx8bRTY3jdYsYg44a0Z/uEgQkohbR0H87Q==
"@typescript-eslint/types@5.18.0":
version "5.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.18.0.tgz#4f0425d85fdb863071680983853c59a62ce9566e"
integrity sha512-bhV1+XjM+9bHMTmXi46p1Led5NP6iqQcsOxgx7fvk6gGiV48c6IynY0apQb7693twJDsXiVzNXTflhplmaiJaw==
"@typescript-eslint/typescript-estree@5.10.1": "@typescript-eslint/typescript-estree@5.10.1":
version "5.10.1" version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.1.tgz#b268e67be0553f8790ba3fe87113282977adda15" resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.1.tgz#b268e67be0553f8790ba3fe87113282977adda15"
@ -991,6 +1040,31 @@
semver "^7.3.5" semver "^7.3.5"
tsutils "^3.21.0" tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@5.18.0":
version "5.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.18.0.tgz#6498e5ee69a32e82b6e18689e2f72e4060986474"
integrity sha512-wa+2VAhOPpZs1bVij9e5gyVu60ReMi/KuOx4LKjGx2Y3XTNUDJgQ+5f77D49pHtqef/klglf+mibuHs9TrPxdQ==
dependencies:
"@typescript-eslint/types" "5.18.0"
"@typescript-eslint/visitor-keys" "5.18.0"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.18.0":
version "5.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.18.0.tgz#27fc84cf95c1a96def0aae31684cb43a37e76855"
integrity sha512-+hFGWUMMri7OFY26TsOlGa+zgjEy1ssEipxpLjtl4wSll8zy85x0GrUSju/FHdKfVorZPYJLkF3I4XPtnCTewA==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.18.0"
"@typescript-eslint/types" "5.18.0"
"@typescript-eslint/typescript-estree" "5.18.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/visitor-keys@5.10.1": "@typescript-eslint/visitor-keys@5.10.1":
version "5.10.1" version "5.10.1"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.1.tgz#29102de692f59d7d34ecc457ed59ab5fc558010b" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.1.tgz#29102de692f59d7d34ecc457ed59ab5fc558010b"
@ -999,11 +1073,38 @@
"@typescript-eslint/types" "5.10.1" "@typescript-eslint/types" "5.10.1"
eslint-visitor-keys "^3.0.0" eslint-visitor-keys "^3.0.0"
"@typescript-eslint/visitor-keys@5.18.0":
version "5.18.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.18.0.tgz#c7c07709823804171d569017f3b031ced7253e60"
integrity sha512-Hf+t+dJsjAKpKSkg3EHvbtEpFFb/1CiOHnvI8bjHgOD4/wAw3gKrA0i94LrbekypiZVanJu3McWJg7rWDMzRTg==
dependencies:
"@typescript-eslint/types" "5.18.0"
eslint-visitor-keys "^3.0.0"
acorn-jsx@^5.3.1: acorn-jsx@^5.3.1:
version "5.3.2" version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn-node@^1.6.1:
version "1.8.2"
resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8"
integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==
dependencies:
acorn "^7.0.0"
acorn-walk "^7.0.0"
xtend "^4.0.2"
acorn-walk@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn@^7.0.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
acorn@^8.7.0: acorn@^8.7.0:
version "8.7.0" version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
@ -1038,6 +1139,19 @@ ansi-styles@^4.1.0:
dependencies: dependencies:
color-convert "^2.0.1" color-convert "^2.0.1"
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.1.tgz#eb0c9a8f77786cad2af8ff2b862899842d7b6adb"
integrity sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==
argparse@^2.0.1: argparse@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@ -1097,11 +1211,30 @@ ast-types-flow@^0.0.7:
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0=
autoprefixer@^10.4.4:
version "10.4.4"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e"
integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA==
dependencies:
browserslist "^4.20.2"
caniuse-lite "^1.0.30001317"
fraction.js "^4.2.0"
normalize-range "^0.1.2"
picocolors "^1.0.0"
postcss-value-parser "^4.2.0"
axe-core@^4.3.5: axe-core@^4.3.5:
version "4.4.1" version "4.4.1"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413"
integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==
axios@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"
axobject-query@^2.2.0: axobject-query@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@ -1121,6 +1254,11 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
version "1.1.11" version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@ -1129,13 +1267,24 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0" balanced-match "^1.0.0"
concat-map "0.0.1" concat-map "0.0.1"
braces@^3.0.2: braces@^3.0.2, braces@~3.0.2:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
dependencies: dependencies:
fill-range "^7.0.1" fill-range "^7.0.1"
browserslist@^4.20.2:
version "4.20.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88"
integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==
dependencies:
caniuse-lite "^1.0.30001317"
electron-to-chromium "^1.4.84"
escalade "^3.1.1"
node-releases "^2.0.2"
picocolors "^1.0.0"
call-bind@^1.0.0, call-bind@^1.0.2: call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
@ -1149,11 +1298,21 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camelcase-css@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001283: caniuse-lite@^1.0.30001283:
version "1.0.30001323" version "1.0.30001323"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001323.tgz#a451ff80dec7033016843f532efda18f02eec011" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001323.tgz#a451ff80dec7033016843f532efda18f02eec011"
integrity sha512-e4BF2RlCVELKx8+RmklSEIVub1TWrmdhvA5kEUueummz1XyySW0DVk+3x9HyhU9MuWTa2BhqLgEuEmUwASAdCA== integrity sha512-e4BF2RlCVELKx8+RmklSEIVub1TWrmdhvA5kEUueummz1XyySW0DVk+3x9HyhU9MuWTa2BhqLgEuEmUwASAdCA==
caniuse-lite@^1.0.30001317:
version "1.0.30001327"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz#c1546d7d7bb66506f0ccdad6a7d07fc6d668c858"
integrity sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==
chalk@^2.0.0: chalk@^2.0.0:
version "2.4.2" version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@ -1163,7 +1322,7 @@ chalk@^2.0.0:
escape-string-regexp "^1.0.5" escape-string-regexp "^1.0.5"
supports-color "^5.3.0" supports-color "^5.3.0"
chalk@^4.0.0: chalk@^4.0.0, chalk@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -1171,6 +1330,26 @@ chalk@^4.0.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
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"
clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
color-convert@^1.9.0: color-convert@^1.9.0:
version "1.9.3" version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
@ -1190,7 +1369,7 @@ color-name@1.1.3:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
color-name@~1.1.4: color-name@^1.1.4, color-name@~1.1.4:
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
@ -1205,6 +1384,11 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
confusing-browser-globals@^1.0.10:
version "1.0.11"
resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81"
integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==
convert-source-map@^1.5.0: convert-source-map@^1.5.0:
version "1.8.0" version "1.8.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
@ -1235,6 +1419,17 @@ cosmiconfig@^6.0.0:
path-type "^4.0.0" path-type "^4.0.0"
yaml "^1.7.2" yaml "^1.7.2"
cosmiconfig@^7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d"
integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==
dependencies:
"@types/parse-json" "^4.0.0"
import-fresh "^3.2.1"
parse-json "^5.0.0"
path-type "^4.0.0"
yaml "^1.10.0"
cross-spawn@^7.0.2: cross-spawn@^7.0.2:
version "7.0.3" version "7.0.3"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@ -1251,6 +1446,11 @@ css-box-model@1.2.1:
dependencies: dependencies:
tiny-invariant "^1.0.6" tiny-invariant "^1.0.6"
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
csstype@3.0.9: csstype@3.0.9:
version "3.0.9" version "3.0.9"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.9.tgz#6410af31b26bd0520933d02cbc64fce9ce3fbf0b"
@ -1299,11 +1499,30 @@ define-properties@^1.1.3:
dependencies: dependencies:
object-keys "^1.0.12" object-keys "^1.0.12"
defined@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
detect-node-es@^1.1.0: detect-node-es@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
detective@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
integrity sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==
dependencies:
acorn-node "^1.6.1"
defined "^1.0.0"
minimist "^1.1.1"
didyoumean@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037"
integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
dir-glob@^3.0.1: dir-glob@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@ -1311,6 +1530,11 @@ dir-glob@^3.0.1:
dependencies: dependencies:
path-type "^4.0.0" path-type "^4.0.0"
dlv@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
doctrine@^2.1.0: doctrine@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d"
@ -1325,6 +1549,11 @@ doctrine@^3.0.0:
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"
electron-to-chromium@^1.4.84:
version "1.4.106"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.106.tgz#e7a3bfa9d745dd9b9e597616cb17283cc349781a"
integrity sha512-ZYfpVLULm67K7CaaGP7DmjyeMY4naxsbTy+syVVxT6QHI1Ww8XbJjmr9fDckrhq44WzCrcC5kH3zGpdusxwwqg==
emoji-regex@^9.2.2: emoji-regex@^9.2.2:
version "9.2.2" version "9.2.2"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
@ -1372,6 +1601,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1" is-date-object "^1.0.1"
is-symbol "^1.0.2" is-symbol "^1.0.2"
escalade@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
escape-string-regexp@^1.0.5: escape-string-regexp@^1.0.5:
version "1.0.5" version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@ -1382,6 +1616,23 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-config-airbnb-base@^15.0.0:
version "15.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236"
integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==
dependencies:
confusing-browser-globals "^1.0.10"
object.assign "^4.1.2"
object.entries "^1.1.5"
semver "^6.3.0"
eslint-config-airbnb-typescript@^17.0.0:
version "17.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.0.0.tgz#360dbcf810b26bbcf2ff716198465775f1c49a07"
integrity sha512-elNiuzD0kPAPTXjFWg+lE24nMdHMtuxgYoD30OyMD6yrW1AhFZPAg27VX7d3tzOErw+dgJTNWfRSDqEcXb4V0g==
dependencies:
eslint-config-airbnb-base "^15.0.0"
eslint-config-next@12.1.4: eslint-config-next@12.1.4:
version "12.1.4" version "12.1.4"
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.1.4.tgz#939ea2ff33034763300bf1e62482cea91212d274" resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.1.4.tgz#939ea2ff33034763300bf1e62482cea91212d274"
@ -1494,6 +1745,14 @@ eslint-plugin-react@7.29.1:
semver "^6.3.0" semver "^6.3.0"
string.prototype.matchall "^4.0.6" string.prototype.matchall "^4.0.6"
eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
dependencies:
esrecurse "^4.3.0"
estraverse "^4.1.1"
eslint-scope@^7.1.1: eslint-scope@^7.1.1:
version "7.1.1" version "7.1.1"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
@ -1583,6 +1842,11 @@ esrecurse@^4.3.0:
dependencies: dependencies:
estraverse "^5.2.0" estraverse "^5.2.0"
estraverse@^4.1.1:
version "4.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0:
version "5.3.0" version "5.3.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
@ -1598,7 +1862,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.2.9: fast-glob@^3.2.11, fast-glob@^3.2.9:
version "3.2.11" version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
@ -1640,6 +1904,13 @@ fill-range@^7.0.1:
dependencies: dependencies:
to-regex-range "^5.0.1" to-regex-range "^5.0.1"
final-form@^4.20.6:
version "4.20.6"
resolved "https://registry.yarnpkg.com/final-form/-/final-form-4.20.6.tgz#da42f3741db068c0c875e18950a2c4a9a148c63e"
integrity sha512-fCdwIj49KOaFfDRlXB57Eo+GghIMZQWrA9TakQI3C9uQxHwaFHXqZSNRlUdfnQmNNeySwGOaGPZCvjy58hyv4w==
dependencies:
"@babel/runtime" "^7.10.0"
find-root@^1.1.0: find-root@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
@ -1672,6 +1943,16 @@ focus-lock@^0.9.1:
dependencies: dependencies:
tslib "^2.0.3" tslib "^2.0.3"
follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
framer-motion@^6: framer-motion@^6:
version "6.2.8" version "6.2.8"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.2.8.tgz#02abb529191af7e2df444185fe27e932215b715d" resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.2.8.tgz#02abb529191af7e2df444185fe27e932215b715d"
@ -1704,6 +1985,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
function-bind@^1.1.1: function-bind@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
@ -1736,14 +2022,14 @@ get-symbol-description@^1.0.0:
call-bind "^1.0.2" call-bind "^1.0.2"
get-intrinsic "^1.1.1" get-intrinsic "^1.1.1"
glob-parent@^5.1.2: glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
dependencies: dependencies:
is-glob "^4.0.1" is-glob "^4.0.1"
glob-parent@^6.0.1: glob-parent@^6.0.1, glob-parent@^6.0.2:
version "6.0.2" version "6.0.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
@ -1839,11 +2125,16 @@ hoist-non-react-statics@^3.3.1:
dependencies: dependencies:
react-is "^16.7.0" react-is "^16.7.0"
ignore@^5.2.0: ignore@^5.1.8, ignore@^5.2.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
immer@^9.0.12:
version "9.0.12"
resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20"
integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA==
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
@ -1898,6 +2189,13 @@ is-bigint@^1.0.1:
dependencies: dependencies:
has-bigints "^1.0.1" has-bigints "^1.0.1"
is-binary-path@~2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
dependencies:
binary-extensions "^2.0.0"
is-boolean-object@^1.1.0: is-boolean-object@^1.1.0:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
@ -1930,7 +2228,7 @@ is-extglob@^2.1.1:
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@ -2055,6 +2353,11 @@ levn@^0.4.1:
prelude-ls "^1.2.1" prelude-ls "^1.2.1"
type-check "~0.4.0" type-check "~0.4.0"
lilconfig@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25"
integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==
lines-and-columns@^1.1.6: lines-and-columns@^1.1.6:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
@ -2112,7 +2415,7 @@ minimatch@^3.0.4, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimist@^1.2.0, minimist@^1.2.6: minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.6:
version "1.2.6" version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
@ -2132,7 +2435,7 @@ ms@^2.1.1:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nanoid@^3.1.30: nanoid@^3.1.30, nanoid@^3.3.1:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.2.tgz#c89622fafb4381cd221421c69ec58547a1eec557"
integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==
@ -2165,11 +2468,31 @@ next@12.1.4:
"@next/swc-win32-ia32-msvc" "12.1.4" "@next/swc-win32-ia32-msvc" "12.1.4"
"@next/swc-win32-x64-msvc" "12.1.4" "@next/swc-win32-x64-msvc" "12.1.4"
node-releases@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01"
integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
normalize-range@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
object-assign@^4.1.1: object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
object-hash@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5"
integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==
object-inspect@^1.12.0, object-inspect@^1.9.0: object-inspect@^1.12.0, object-inspect@^1.9.0:
version "1.12.0" version "1.12.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0"
@ -2310,7 +2633,7 @@ picocolors@^1.0.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.3.1: picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
@ -2325,6 +2648,41 @@ popmotion@11.0.3:
style-value-types "5.0.0" style-value-types "5.0.0"
tslib "^2.1.0" tslib "^2.1.0"
postcss-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00"
integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==
dependencies:
camelcase-css "^2.0.1"
postcss-load-config@^3.1.0:
version "3.1.4"
resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855"
integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==
dependencies:
lilconfig "^2.0.5"
yaml "^1.10.2"
postcss-nested@5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc"
integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==
dependencies:
postcss-selector-parser "^6.0.6"
postcss-selector-parser@^6.0.6, postcss-selector-parser@^6.0.9:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@8.4.5: postcss@8.4.5:
version "8.4.5" version "8.4.5"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
@ -2334,6 +2692,15 @@ postcss@8.4.5:
picocolors "^1.0.0" picocolors "^1.0.0"
source-map-js "^1.0.1" source-map-js "^1.0.1"
postcss@^8.4.12, postcss@^8.4.6:
version "8.4.12"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905"
integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==
dependencies:
nanoid "^3.3.1"
picocolors "^1.0.0"
source-map-js "^1.0.2"
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -2358,6 +2725,11 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
quick-lru@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
react-clientside-effect@^1.2.5: react-clientside-effect@^1.2.5:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3" resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3"
@ -2378,6 +2750,13 @@ react-fast-compare@3.2.0:
resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb" resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.0.tgz#641a9da81b6a6320f270e89724fb45a0b39e43bb"
integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA== integrity sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==
react-final-form@^6.5.9:
version "6.5.9"
resolved "https://registry.yarnpkg.com/react-final-form/-/react-final-form-6.5.9.tgz#644797d4c122801b37b58a76c87761547411190b"
integrity sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==
dependencies:
"@babel/runtime" "^7.15.4"
react-focus-lock@2.5.2: react-focus-lock@2.5.2:
version "2.5.2" version "2.5.2"
resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.2.tgz#f1e4db5e25cd8789351f2bd5ebe91e9dcb9c2922" resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.5.2.tgz#f1e4db5e25cd8789351f2bd5ebe91e9dcb9c2922"
@ -2390,6 +2769,11 @@ react-focus-lock@2.5.2:
use-callback-ref "^1.2.5" use-callback-ref "^1.2.5"
use-sidecar "^1.0.5" use-sidecar "^1.0.5"
react-icons@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca"
integrity sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==
react-is@^16.13.1, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -2430,6 +2814,13 @@ react@18.0.0:
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
readdirp@~3.6.0:
version "3.6.0"
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
dependencies:
picomatch "^2.2.1"
regenerator-runtime@^0.13.4: regenerator-runtime@^0.13.4:
version "0.13.9" version "0.13.9"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
@ -2453,7 +2844,7 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0: resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.22.0:
version "1.22.0" version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
@ -2539,7 +2930,7 @@ slash@^3.0.0:
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
source-map-js@^1.0.1: source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@ -2633,11 +3024,43 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
swr@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8"
integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw==
systeminformation@^5.11.9: systeminformation@^5.11.9:
version "5.11.9" version "5.11.9"
resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.9.tgz#95f2334e739dd224178948a2afaced7d9abfdf9d" resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.11.9.tgz#95f2334e739dd224178948a2afaced7d9abfdf9d"
integrity sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA== integrity sha512-eeMtL9UJFR/LYG+2rpeAgZ0Va4ojlNQTkYiQH/xbbPwDjDMsaetj3Pkc+C1aH5G8mav6HvDY8kI4Vl4noksSkA==
tailwindcss@^3.0.23:
version "3.0.23"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.0.23.tgz#c620521d53a289650872a66adfcb4129d2200d10"
integrity sha512-+OZOV9ubyQ6oI2BXEhzw4HrqvgcARY38xv3zKcjnWtMIZstEsXdI9xftd1iB7+RbOnj2HOEzkA0OyB5BaSxPQA==
dependencies:
arg "^5.0.1"
chalk "^4.1.2"
chokidar "^3.5.3"
color-name "^1.1.4"
cosmiconfig "^7.0.1"
detective "^5.2.0"
didyoumean "^1.2.2"
dlv "^1.1.3"
fast-glob "^3.2.11"
glob-parent "^6.0.2"
is-glob "^4.0.3"
normalize-path "^3.0.0"
object-hash "^2.2.0"
postcss "^8.4.6"
postcss-js "^4.0.0"
postcss-load-config "^3.1.0"
postcss-nested "5.0.6"
postcss-selector-parser "^6.0.9"
postcss-value-parser "^4.2.0"
quick-lru "^5.1.1"
resolve "^1.22.0"
text-table@^0.2.0: text-table@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@ -2739,6 +3162,11 @@ use-sidecar@^1.0.1, use-sidecar@^1.0.5:
detect-node-es "^1.1.0" detect-node-es "^1.1.0"
tslib "^1.9.3" tslib "^1.9.3"
util-deprecate@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
v8-compile-cache@^2.0.3: v8-compile-cache@^2.0.3:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
@ -2784,12 +3212,22 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
xtend@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yallist@^4.0.0: yallist@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml@^1.7.2: yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
version "1.10.2" version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
zustand@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.2.tgz#7b44c4f4a5bfd7a8296a3957b13e1c346f42514d"
integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==

View file

@ -1,4 +1,4 @@
version: '3.7' version: "3.7"
services: services:
# gluetun: # gluetun:
@ -18,42 +18,48 @@ services:
# - 8080:8080 # - 8080:8080
# networks: # networks:
# - tipi_main_network # - tipi_main_network
reverse-proxy: # reverse-proxy:
container_name: reverse-proxy # container_name: reverse-proxy
image: traefik:v2.6 # image: traefik:v2.6
restart: always # restart: always
ports: # ports:
- 80:80 # - 80:80
- 443:443 # - 443:443
- 8080:8080 # - 8080:8080
security_opt: # security_opt:
- no-new-privileges:true # - no-new-privileges:true
volumes: # volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # - /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/traefik:/root/.config # - ${PWD}/traefik:/root/.config
networks: # networks:
- tipi_main_network # - tipi_main_network
dashboard: dashboard:
build: build:
context: ./dashboard context: ./dashboard
dockerfile: Dockerfile dockerfile: Dockerfile
args:
INTERNAL_IP_ARG: ${INTERNAL_IP}
container_name: dashboard container_name: dashboard
volumes: volumes:
- ${PWD}/state:/app/state - ${PWD}/state:/app/state
- ${PWD}/config:/app/config:ro
ports: ports:
- 3000:3000 - 3000:3000
networks: networks:
- tipi_main_network - tipi_main_network
labels: # labels:
traefik.enable: true # traefik.enable: true
traefik.http.routers.dashboard.rule: Host(`tipi.local`) # traefik.http.routers.dashboard.rule: Host(`tipi.local`)
# traefik.http.routers.dashboard.tls: true # # traefik.http.routers.dashboard.tls: true
traefik.http.routers.dashboard.entrypoints: webinsecure # traefik.http.routers.dashboard.entrypoints: webinsecure
traefik.http.routers.dashboard.service: dashboard # traefik.http.routers.dashboard.service: dashboard
traefik.http.services.dashboard.loadbalancer.server.port: 3000 # traefik.http.services.dashboard.loadbalancer.server.port: 3000
networks: networks:
tipi_main_network: tipi_main_network:
driver: bridge
ipam:
driver: default
config:
- subnet: 10.21.21.0/24

View file

@ -87,12 +87,17 @@ compose() {
# Vars to use in compose file # Vars to use in compose file
export APP_DATA_DIR="${app_data_dir}" export APP_DATA_DIR="${app_data_dir}"
export APP_PASSWORD="password"
export APP_DIR="${app_dir}" export APP_DIR="${app_dir}"
# TODO: Fix for dynamic detection
export DEVICE_IP="192.168.2.132"
export ROOT_FOLDER="${ROOT_FOLDER}"
# Docker-compose does not support multiple env files
# --env-file "${env_file}" \
docker-compose \ docker-compose \
--env-file "${env_file}" \ --env-file "${ROOT_FOLDER}/app-data/${app}/app.env" \
--env-file "${app_dir}/.env" \
--project-name "${app}" \ --project-name "${app}" \
--file "${app_compose_file}" \ --file "${app_compose_file}" \
--file "${common_compose_file}" \ --file "${common_compose_file}" \
@ -103,24 +108,25 @@ compose() {
if [[ "$command" = "install" ]]; then if [[ "$command" = "install" ]]; then
compose "${app}" pull compose "${app}" pull
# Copy default data dir to app data dir if it exists
if [[ -d "${ROOT_FOLDER}/apps/${app}/data" ]]; then
cp -r "${ROOT_FOLDER}/apps/${app}/data" "${app_data_dir}/data"
fi
compose "${app}" up -d compose "${app}" up -d
exit exit
fi fi
# Removes images and destroys all data for an app # Removes images and destroys all data for an app
if [[ "$command" = "uninstall" ]]; then if [[ "$command" = "uninstall" ]]; then
echo "Removing images for app ${app}..." echo "Removing images for app ${app}..."
compose "${app}" down --rmi all --remove-orphans compose "${app}" down --remove-orphans
echo "Deleting app data for app ${app}..." echo "Deleting app data for app ${app}..."
if [[ -d "${app_data_dir}" ]]; then if [[ -d "${app_data_dir}" ]]; then
rm -rf "${app_data_dir}" rm -rf "${app_data_dir}"
fi fi
echo "Removing app ${app} from DB..."
update_installed_apps remove "${app}"
echo "Successfully uninstalled app ${app}" echo "Successfully uninstalled app ${app}"
exit exit
fi fi

View file

@ -1,14 +1,7 @@
ROOT_FOLDER="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)" #!/usr/bin/env bash
set -e # Exit immediately if a command exits with a non-zero status.
# Constants ROOT_FOLDER="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
NGINX_PORT="80"
# Apps
APP_PI_HOLE_PORT="8081"
APP_WG_EASY_PORT="8082"
APP_NEXTCLOUD_PORT="8082"
APP_ANONADDY_PORT="8083"
APP_SIMPLETORRENT_PORT="8084"
APP_FRESHRSS_PORT="8085"
echo echo
echo "======================================" echo "======================================"
@ -21,43 +14,19 @@ echo "=============== TIPI ================="
echo "======================================" echo "======================================"
echo echo
# Store paths to intermediary config files
ENV_FILE="./templates/.env"
# Remove intermediary config files
[[ -f "$ENV_FILE" ]] && rm -f "$ENV_FILE"
# Copy template configs to intermediary configs
[[ -f "./templates/.env-sample" ]] && cp "./templates/.env-sample" "$ENV_FILE"
# Install ansible if not installed # Install ansible if not installed
if ! command -v ansible-playbook > /dev/null; then if ! command -v ansible-playbook > /dev/null; then
echo "Installing Ansible..." echo "Installing Ansible..."
sudo apt-get install -y software-properties-common
sudo apt-add-repository -y ppa:ansible/ansible
sudo apt-get update sudo apt-get update
sudo apt-get install -y ansible sudo apt-get install python3 python3-pip -y
sudo pip3 install ansible
fi fi
ansible-playbook ansible/setup.yml -i ansible/hosts -K ansible-playbook ansible/setup.yml -i ansible/hosts -K
echo "Generating config files..." # echo "Configuring permissions..."
for template in "${ENV_FILE}"; do # echo
sed -i "s/<nginx-port>/${NGINX_PORT}/g" "${template}" # find "$ROOT_FOLDER" -path "$ROOT_FOLDER/app-data" -prune -o -exec chown 1000:1000 {} + || true
# Apps
sed -i "s/<app-pi-hole-port>/${APP_PI_HOLE_PORT}/g" "${template}"
sed -i "s/<app-wgeasy-port>/${APP_WG_EASY_PORT}/g" "${template}"
sed -i "s/<app-nextcloud-port>/${APP_NEXTCLOUD_PORT}/g" "${template}"
sed -i "s/<app-anonaddy-port>/${APP_ANONADDY_PORT}/g" "${template}"
sed -i "s/<app-simpletorrent-port>/${APP_SIMPLETORRENT_PORT}/g" "${template}"
sed -i "s/<app-freshrss-port>/${APP_FRESHRSS_PORT}/g" "${template}"
done
mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
echo "Configuring permissions..."
echo
find "$ROOT_FOLDER" -path "$ROOT_FOLDER/app-data" -prune -o -exec chown 1000:1000 {} + || true
# Create configured status # Create configured status
touch "${ROOT_FOLDER}/state/configured" touch "${ROOT_FOLDER}/state/configured"

View file

@ -1,4 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e # Exit immediately if a command exits with a non-zero status.
# use greadlink instead of readlink on osx # use greadlink instead of readlink on osx
if [[ "$(uname)" == "Darwin" ]]; then if [[ "$(uname)" == "Darwin" ]]; then
@ -9,7 +10,11 @@ fi
ROOT_FOLDER="$($readlink -f $(dirname "${BASH_SOURCE[0]}")/..)" ROOT_FOLDER="$($readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
STATE_FOLDER="${ROOT_FOLDER}/state" STATE_FOLDER="${ROOT_FOLDER}/state"
DOMAIN=local
INTERNAL_IP="$(hostname -I | awk '{print $1}')"
PUID="$(id -u)"
PGID="$(id -g)"
TZ="$(cat /etc/timezone | sed 's/\//\\\//g' || echo "Europe/Berlin")"
if [[ $UID != 0 ]]; then if [[ $UID != 0 ]]; then
echo "Tipi must be started as root" echo "Tipi must be started as root"
@ -23,13 +28,40 @@ if [[ ! -f "${STATE_FOLDER}/configured" ]]; then
"${ROOT_FOLDER}/scripts/configure.sh" "${ROOT_FOLDER}/scripts/configure.sh"
fi fi
ansible-playbook ansible/start.yml -i ansible/hosts -K # Copy the app state if it isn't here
if [[ ! -f "${STATE_FOLDER}/apps.json" ]]; then
cp "${ROOT_FOLDER}/templates/apps-sample.json" "${STATE_FOLDER}/apps.json"
fi
export DOCKER_CLIENT_TIMEOUT=240 export DOCKER_CLIENT_TIMEOUT=240
export COMPOSE_HTTP_TIMEOUT=240 export COMPOSE_HTTP_TIMEOUT=240
echo "Generating config files..."
# Remove current .env file
[[ -f "${ROOT_FOLDER}/.env" ]] && rm -f "${ROOT_FOLDER}/.env"
# Store paths to intermediary config files
ENV_FILE="$ROOT_FOLDER/templates/.env"
# Remove intermediary config files
[[ -f "$ENV_FILE" ]] && rm -f "$ENV_FILE"
# Copy template configs to intermediary configs
[[ -f "$ROOT_FOLDER/templates/env-sample" ]] && cp "$ROOT_FOLDER/templates/env-sample" "$ENV_FILE"
for template in "${ENV_FILE}"; do
sed -i "s/<internal_ip>/${INTERNAL_IP}/g" "${template}"
sed -i "s/<puid>/${PUID}/g" "${template}"
sed -i "s/<pgid>/${PGID}/g" "${template}"
sed -i "s/<tz>/${TZ}/g" "${template}"
done
mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
ansible-playbook ansible/start.yml -i ansible/hosts -K
# Run docker-compose # Run docker-compose
docker-compose up --detach --remove-orphans --build || { docker-compose --env-file "${ROOT_FOLDER}/.env" up --detach --remove-orphans --build || {
echo "Failed to start containers" echo "Failed to start containers"
exit 1 exit 1
} }
@ -49,3 +81,32 @@ for app in "${apps_to_start[@]}"; do
"${ROOT_FOLDER}/scripts/app.sh" start $app "${ROOT_FOLDER}/scripts/app.sh" start $app
done done
echo "Tipi is now running"
echo ""
cat << "EOF"
_,.
,` -.)
'( _/'-\\-.
/,|`--._,-^| ,
\_| |`-._/|| ,'|
| `-, / | / /
| || | / /
`r-._||/ __ / /
__,-<_ )`-/ `./ /
' \ `---' \ / /
| |./ /
/ // /
\_/' \ |/ /
| | _,^-'/ /
| , `` (\/ /_
\,.->._ \X-=/^
( / `-._//^`
`Y-.____(__}
| {__)
()`
EOF
echo ""
echo "Visit http://${INTERNAL_IP}:3000 to view the dashboard"
echo ""

View file

@ -1,6 +0,0 @@
{
"installed": "",
"environment": {
"anonaddy": {}
}
}

View file

@ -1,16 +1,18 @@
module.exports = { module.exports = {
env: { node: true }, env: { node: true },
extends: ['airbnb-base', 'eslint:recommended', 'plugin:import/typescript'], extends: ['airbnb-typescript', 'eslint:recommended', 'plugin:import/typescript'],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
ecmaVersion: 'latest', ecmaVersion: 'latest',
sourceType: 'module', sourceType: 'module',
}, },
plugins: ['@typescript-eslint', 'import'], plugins: ['@typescript-eslint', 'import', 'react'],
rules: { rules: {
'arrow-body-style': 0, 'arrow-body-style': 0,
'no-restricted-exports': 0, 'no-restricted-exports': 0,
'max-len': [{ code: 200 }], 'max-len': [1, { code: 200 }],
'import/extensions': ['error', 'ignorePackages', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' }], 'import/extensions': ['error', 'ignorePackages', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' }],
}, },
}; };

View file

@ -10,16 +10,22 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"compression": "^1.7.4", "compression": "^1.7.4",
"cors": "^2.8.5",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"helmet": "^5.0.2", "helmet": "^5.0.2",
"internal-ip": "^7.0.0",
"node-port-scanner": "^3.0.1", "node-port-scanner": "^3.0.1",
"p-iteration": "^1.1.8",
"public-ip": "^5.0.0", "public-ip": "^5.0.0",
"systeminformation": "^5.11.9" "systeminformation": "^5.11.9",
"tcp-port-used": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/compression": "^1.7.2", "@types/compression": "^1.7.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/tcp-port-used": "^1.0.1",
"@types/validator": "^13.7.2", "@types/validator": "^13.7.2",
"concurrently": "^7.1.0", "concurrently": "^7.1.0",
"esbuild": "^0.14.32", "esbuild": "^0.14.32",
@ -27,8 +33,10 @@
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-hardcore": "^24.5.0", "eslint-config-hardcore": "^24.5.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-config-react": "^1.1.7",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-unicorn": "^42.0.0", "eslint-plugin-unicorn": "^42.0.0",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"prettier": "2.6.2" "prettier": "2.6.2"
@ -3176,6 +3184,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/cors": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
"dev": true
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.7", "version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
@ -3336,6 +3350,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/tcp-port-used": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.1.tgz",
"integrity": "sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==",
"dev": true
},
"node_modules/@types/unist": { "node_modules/@types/unist": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
@ -4841,6 +4861,18 @@
"url": "https://opencollective.com/core-js" "url": "https://opencollective.com/core-js"
} }
}, },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/cosmiconfig": { "node_modules/cosmiconfig": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
@ -4909,7 +4941,6 @@
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@ -5081,8 +5112,7 @@
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
"dev": true
}, },
"node_modules/deepmerge": { "node_modules/deepmerge": {
"version": "4.2.2", "version": "4.2.2",
@ -5093,6 +5123,17 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/default-gateway": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz",
"integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==",
"dependencies": {
"execa": "^5.0.0"
},
"engines": {
"node": ">= 10"
}
},
"node_modules/defer-to-connect": { "node_modules/defer-to-connect": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@ -5921,6 +5962,12 @@
"eslint": ">=7.0.0" "eslint": ">=7.0.0"
} }
}, },
"node_modules/eslint-config-react": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/eslint-config-react/-/eslint-config-react-1.1.7.tgz",
"integrity": "sha1-oJGND8R9DpvRYaRzCAIdqF0lhbM=",
"dev": true
},
"node_modules/eslint-etc": { "node_modules/eslint-etc": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-etc/-/eslint-etc-5.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-etc/-/eslint-etc-5.1.0.tgz",
@ -7095,6 +7142,28 @@
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
"dev": true "dev": true
}, },
"node_modules/execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"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"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/execall": { "node_modules/execall": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz",
@ -7956,6 +8025,14 @@
"node": ">=10.19.0" "node": ">=10.19.0"
} }
}, },
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/iconv-lite": { "node_modules/iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -8090,6 +8167,31 @@
"node": ">=10" "node": ">=10"
} }
}, },
"node_modules/internal-ip": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-7.0.0.tgz",
"integrity": "sha512-qE4TeD4brqC45Vq/+VASeMiS1KRyfBkR6HT2sh9pZVVCzSjPkaCEfKFU+dL0PRv7NHJtvoKN2r82G6wTfzorkw==",
"dependencies": {
"default-gateway": "^6.0.3",
"ipaddr.js": "^2.0.1",
"is-ip": "^3.1.0",
"p-event": "^4.2.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sindresorhus/internal-ip?sponsor=1"
}
},
"node_modules/internal-ip/node_modules/ipaddr.js": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
"integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==",
"engines": {
"node": ">= 10"
}
},
"node_modules/internal-slot": { "node_modules/internal-slot": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
@ -8514,6 +8616,17 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-string": { "node_modules/is-string": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@ -8562,6 +8675,11 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-url": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
},
"node_modules/is-weakref": { "node_modules/is-weakref": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@ -8580,11 +8698,23 @@
"integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
"dev": true "dev": true
}, },
"node_modules/is2": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz",
"integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==",
"dependencies": {
"deep-is": "^0.1.3",
"ip-regex": "^4.1.0",
"is-url": "^1.2.4"
},
"engines": {
"node": ">=v0.10.0"
}
},
"node_modules/isexe": { "node_modules/isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
"dev": true
}, },
"node_modules/isomorphic-git": { "node_modules/isomorphic-git": {
"version": "1.17.0", "version": "1.17.0",
@ -8938,6 +9068,7 @@
"version": "7.8.0", "version": "7.8.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.0.tgz",
"integrity": "sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg==", "integrity": "sha512-AmXqneQZL3KZMIgBpaPTeI6pfwh+xQ2vutMsyqOu1TBdEXFZgpG/80wuJ531w2ZN7TI0/oc8CPxzh/DKQudZqg==",
"deprecated": "Please update to latest patch version to fix memory leak https://github.com/isaacs/node-lru-cache/issues/227",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@ -9310,6 +9441,11 @@
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
}, },
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"node_modules/merge2": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -9853,6 +9989,14 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"engines": {
"node": ">=6"
}
},
"node_modules/mimic-response": { "node_modules/mimic-response": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@ -10126,6 +10270,17 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/obj-props": { "node_modules/obj-props": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.4.0.tgz", "resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.4.0.tgz",
@ -10139,7 +10294,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -10268,6 +10422,20 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dependencies": {
"mimic-fn": "^2.1.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@ -10293,6 +10461,36 @@
"node": ">=12.20" "node": ">=12.20"
} }
}, },
"node_modules/p-event": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz",
"integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==",
"dependencies": {
"p-timeout": "^3.1.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
"engines": {
"node": ">=4"
}
},
"node_modules/p-iteration": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.8.tgz",
"integrity": "sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/p-limit": { "node_modules/p-limit": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
@ -10317,6 +10515,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/p-timeout": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
"dependencies": {
"p-finally": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": { "node_modules/p-try": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
@ -10630,7 +10839,6 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -12314,7 +12522,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": { "dependencies": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
}, },
@ -12326,7 +12533,6 @@
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@ -12348,8 +12554,7 @@
"node_modules/signal-exit": { "node_modules/signal-exit": {
"version": "3.0.7", "version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
"dev": true
}, },
"node_modules/simple-concat": { "node_modules/simple-concat": {
"version": "1.0.1", "version": "1.0.1",
@ -12645,6 +12850,14 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
"engines": {
"node": ">=6"
}
},
"node_modules/strip-indent": { "node_modules/strip-indent": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@ -13146,6 +13359,36 @@
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true "dev": true
}, },
"node_modules/tcp-port-used": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz",
"integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==",
"dependencies": {
"debug": "4.3.1",
"is2": "^2.0.6"
}
},
"node_modules/tcp-port-used/node_modules/debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/tcp-port-used/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/text-table": { "node_modules/text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -13915,7 +14158,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": { "dependencies": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
}, },
@ -16327,6 +16569,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/cors": {
"version": "2.8.12",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==",
"dev": true
},
"@types/debug": { "@types/debug": {
"version": "4.1.7", "version": "4.1.7",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
@ -16487,6 +16735,12 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/tcp-port-used": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/tcp-port-used/-/tcp-port-used-1.0.1.tgz",
"integrity": "sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==",
"dev": true
},
"@types/unist": { "@types/unist": {
"version": "2.0.6", "version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
@ -17557,6 +17811,15 @@
"integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==",
"dev": true "dev": true
}, },
"cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"requires": {
"object-assign": "^4",
"vary": "^1"
}
},
"cosmiconfig": { "cosmiconfig": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz",
@ -17613,7 +17876,6 @@
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"requires": { "requires": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@ -17733,8 +17995,7 @@
"deep-is": { "deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
"dev": true
}, },
"deepmerge": { "deepmerge": {
"version": "4.2.2", "version": "4.2.2",
@ -17742,6 +18003,14 @@
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true "dev": true
}, },
"default-gateway": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz",
"integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==",
"requires": {
"execa": "^5.0.0"
}
},
"defer-to-connect": { "defer-to-connect": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
@ -18320,6 +18589,12 @@
"dev": true, "dev": true,
"requires": {} "requires": {}
}, },
"eslint-config-react": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/eslint-config-react/-/eslint-config-react-1.1.7.tgz",
"integrity": "sha1-oJGND8R9DpvRYaRzCAIdqF0lhbM=",
"dev": true
},
"eslint-etc": { "eslint-etc": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/eslint-etc/-/eslint-etc-5.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-etc/-/eslint-etc-5.1.0.tgz",
@ -19125,6 +19400,22 @@
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
"dev": true "dev": true
}, },
"execa": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
"integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
"requires": {
"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"
}
},
"execall": { "execall": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz",
@ -19769,6 +20060,11 @@
"resolve-alpn": "^1.2.0" "resolve-alpn": "^1.2.0"
} }
}, },
"human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
},
"iconv-lite": { "iconv-lite": {
"version": "0.4.24", "version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -19861,6 +20157,24 @@
"integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
"dev": true "dev": true
}, },
"internal-ip": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-7.0.0.tgz",
"integrity": "sha512-qE4TeD4brqC45Vq/+VASeMiS1KRyfBkR6HT2sh9pZVVCzSjPkaCEfKFU+dL0PRv7NHJtvoKN2r82G6wTfzorkw==",
"requires": {
"default-gateway": "^6.0.3",
"ipaddr.js": "^2.0.1",
"is-ip": "^3.1.0",
"p-event": "^4.2.0"
},
"dependencies": {
"ipaddr.js": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz",
"integrity": "sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng=="
}
}
},
"internal-slot": { "internal-slot": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
@ -20157,6 +20471,11 @@
"call-bind": "^1.0.2" "call-bind": "^1.0.2"
} }
}, },
"is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="
},
"is-string": { "is-string": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@ -20190,6 +20509,11 @@
"unc-path-regex": "^0.1.2" "unc-path-regex": "^0.1.2"
} }
}, },
"is-url": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
"integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww=="
},
"is-weakref": { "is-weakref": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
@ -20205,11 +20529,20 @@
"integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==",
"dev": true "dev": true
}, },
"is2": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/is2/-/is2-2.0.7.tgz",
"integrity": "sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==",
"requires": {
"deep-is": "^0.1.3",
"ip-regex": "^4.1.0",
"is-url": "^1.2.4"
}
},
"isexe": { "isexe": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
"dev": true
}, },
"isomorphic-git": { "isomorphic-git": {
"version": "1.17.0", "version": "1.17.0",
@ -20762,6 +21095,11 @@
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
}, },
"merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
},
"merge2": { "merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -21058,6 +21396,11 @@
"mime-db": "1.52.0" "mime-db": "1.52.0"
} }
}, },
"mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
},
"mimic-response": { "mimic-response": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
@ -21273,6 +21616,14 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
"integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="
}, },
"npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"requires": {
"path-key": "^3.0.0"
}
},
"obj-props": { "obj-props": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.4.0.tgz", "resolved": "https://registry.npmjs.org/obj-props/-/obj-props-1.4.0.tgz",
@ -21282,8 +21633,7 @@
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
"dev": true
}, },
"object-inspect": { "object-inspect": {
"version": "1.12.0", "version": "1.12.0",
@ -21373,6 +21723,14 @@
"wrappy": "1" "wrappy": "1"
} }
}, },
"onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"requires": {
"mimic-fn": "^2.1.0"
}
},
"optionator": { "optionator": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@ -21392,6 +21750,24 @@
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
"integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw=="
}, },
"p-event": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz",
"integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==",
"requires": {
"p-timeout": "^3.1.0"
}
},
"p-finally": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
},
"p-iteration": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.8.tgz",
"integrity": "sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ=="
},
"p-limit": { "p-limit": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
@ -21410,6 +21786,14 @@
"p-limit": "^1.1.0" "p-limit": "^1.1.0"
} }
}, },
"p-timeout": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
"requires": {
"p-finally": "^1.0.0"
}
},
"p-try": { "p-try": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
@ -21669,8 +22053,7 @@
"path-key": { "path-key": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
"dev": true
}, },
"path-parse": { "path-parse": {
"version": "1.0.7", "version": "1.0.7",
@ -22948,7 +23331,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"requires": { "requires": {
"shebang-regex": "^3.0.0" "shebang-regex": "^3.0.0"
} }
@ -22956,8 +23338,7 @@
"shebang-regex": { "shebang-regex": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
"dev": true
}, },
"side-channel": { "side-channel": {
"version": "1.0.4", "version": "1.0.4",
@ -22973,8 +23354,7 @@
"signal-exit": { "signal-exit": {
"version": "3.0.7", "version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
"dev": true
}, },
"simple-concat": { "simple-concat": {
"version": "1.0.1", "version": "1.0.1",
@ -23192,6 +23572,11 @@
"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
"dev": true "dev": true
}, },
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
"integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="
},
"strip-indent": { "strip-indent": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@ -23557,6 +23942,30 @@
} }
} }
}, },
"tcp-port-used": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tcp-port-used/-/tcp-port-used-1.0.2.tgz",
"integrity": "sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==",
"requires": {
"debug": "4.3.1",
"is2": "^2.0.6"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"text-table": { "text-table": {
"version": "0.2.0", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
@ -24144,7 +24553,6 @@
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"requires": { "requires": {
"isexe": "^2.0.0" "isexe": "^2.0.0"
} }

View file

@ -17,16 +17,22 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"compression": "^1.7.4", "compression": "^1.7.4",
"cors": "^2.8.5",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.17.3", "express": "^4.17.3",
"helmet": "^5.0.2", "helmet": "^5.0.2",
"internal-ip": "^7.0.0",
"node-port-scanner": "^3.0.1", "node-port-scanner": "^3.0.1",
"p-iteration": "^1.1.8",
"public-ip": "^5.0.0", "public-ip": "^5.0.0",
"systeminformation": "^5.11.9" "systeminformation": "^5.11.9",
"tcp-port-used": "^1.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/compression": "^1.7.2", "@types/compression": "^1.7.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13", "@types/express": "^4.17.13",
"@types/tcp-port-used": "^1.0.1",
"@types/validator": "^13.7.2", "@types/validator": "^13.7.2",
"concurrently": "^7.1.0", "concurrently": "^7.1.0",
"esbuild": "^0.14.32", "esbuild": "^0.14.32",
@ -34,8 +40,10 @@
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-hardcore": "^24.5.0", "eslint-config-hardcore": "^24.5.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.5.0",
"eslint-config-react": "^1.1.7",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-unicorn": "^42.0.0", "eslint-plugin-unicorn": "^42.0.0",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"prettier": "2.6.2" "prettier": "2.6.2"

View file

@ -0,0 +1 @@
export const appNames = ['nextcloud', 'freshrss', 'anonaddy', 'filerun', 'wg-easy', 'radarr', 'transmission', 'jellyfin', 'pihole', 'busybox'];

View file

@ -1,15 +1,34 @@
export enum FieldTypes {
text = 'text',
password = 'password',
email = 'email',
number = 'number',
fqdn = 'fqdn',
}
interface FormField { interface FormField {
type: string; type: FieldTypes;
label: string; label: string;
max?: number; max?: number;
min?: number; min?: number;
required?: boolean; required?: boolean;
env_variable?: string; env_variable: string;
} }
export interface AppConfig { export interface AppConfig {
id: string;
port: number;
name: string; name: string;
requirements?: {
ports?: number[];
};
description: string; description: string;
version: string; version: string;
image: string;
form_fields: Record<string, FormField>; form_fields: Record<string, FormField>;
short_desc: string;
author: string;
source: string;
installed: boolean;
status: 'running' | 'stopped';
} }

View file

@ -1,6 +1,9 @@
import { Request, Response } from 'express'; import { NextFunction, Request, Response } from 'express';
import si from 'systeminformation';
import { appNames } from '../../config/apps';
import { AppConfig } from '../../config/types'; import { AppConfig } from '../../config/types';
import { createFolder, fileExists, readJsonFile, writeFile, copyFile, runScript, deleteFolder } from '../fs/fs.helpers'; import { createFolder, fileExists, readJsonFile, writeFile, readFile } from '../fs/fs.helpers';
import { checkAppExists, checkAppRequirements, checkEnvFile, ensureAppState, getInitalFormValues, runAppScript } from './apps.helpers';
type AppsState = { installed: string }; type AppsState = { installed: string };
@ -9,14 +12,9 @@ const getStateFile = (): AppsState => {
}; };
const generateEnvFile = (appName: string, form: Record<string, string>) => { const generateEnvFile = (appName: string, form: Record<string, string>) => {
const appExists = fileExists(`/app-data/${appName}`);
if (!appExists) {
throw new Error(`App ${appName} not installed`);
}
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`); const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`);
let envFile = ''; const baseEnvFile = readFile('/.env').toString();
let envFile = `${baseEnvFile}\nAPP_PORT=${configFile.port}\n`;
Object.keys(configFile.form_fields).forEach((key) => { Object.keys(configFile.form_fields).forEach((key) => {
const value = form[key]; const value = form[key];
@ -29,151 +27,191 @@ const generateEnvFile = (appName: string, form: Record<string, string>) => {
} }
}); });
writeFile(`/app-data/${appName}/.env`, envFile); writeFile(`/app-data/${appName}/app.env`, envFile);
}; };
const installApp = (req: Request, res: Response) => { const uninstallApp = async (req: Request, res: Response, next: NextFunction) => {
try { try {
const { appName, form } = req.body; const { id: appName } = req.params;
if (!appName) { if (!appName) {
throw new Error('App name is required'); throw new Error('App name is required');
} }
const appExists = fileExists(`/app-data/${appName}`); checkAppExists(appName);
ensureAppState(appName, false);
if (appExists) {
throw new Error(`App ${appName} already installed`);
}
// Create app folder
createFolder(`/app-data/${appName}`);
// Copy default app files from app-data folder
copyFile(`/apps/${appName}/data`, `/app-data/${appName}/data`);
// Create env file
generateEnvFile(appName, form);
const state = getStateFile();
state.installed += ` ${appName}`;
writeFile('/state/apps.json', JSON.stringify(state));
// Run script // Run script
runScript('/scripts/app.sh', ['install', appName]); await runAppScript(['uninstall', appName]);
res.status(200).json({ message: 'App installed successfully' });
} catch (e) {
res.status(500).send(e);
}
};
const uninstallApp = (req: Request, res: Response) => {
try {
const { appName } = req.body;
if (!appName) {
throw new Error('App name is required');
}
const appExists = fileExists(`/app-data/${appName}`);
if (!appExists) {
throw new Error(`App ${appName} not installed`);
}
// Delete app folder
deleteFolder(`/app-data/${appName}`);
// Remove app from apps.json
const state = getStateFile();
state.installed = state.installed.replace(` ${appName}`, '');
writeFile('/state/apps.json', JSON.stringify(state));
// Run script
runScript('/scripts/app.sh', ['uninstall', appName]);
res.status(200).json({ message: 'App uninstalled successfully' }); res.status(200).json({ message: 'App uninstalled successfully' });
} catch (e) { } catch (e) {
res.status(500).send(e); next(e);
} }
}; };
const stopApp = (req: Request, res: Response) => { const stopApp = async (req: Request, res: Response, next: NextFunction) => {
try { try {
const { appName } = req.body; const { id: appName } = req.params;
if (!appName) { if (!appName) {
throw new Error('App name is required'); throw new Error('App name is required');
} }
const appExists = fileExists(`/app-data/${appName}`); checkAppExists(appName);
if (!appExists) {
throw new Error(`App ${appName} not installed`);
}
// Run script // Run script
runScript('/scripts/app.sh', ['stop', appName]); await runAppScript(['stop', appName]);
res.status(200).json({ message: 'App stopped successfully' }); res.status(200).json({ message: 'App stopped successfully' });
} catch (e) { } catch (e) {
res.status(500).end(e); next(e);
} }
}; };
const updateAppConfig = (req: Request, res: Response) => { const updateAppConfig = async (req: Request, res: Response, next: NextFunction) => {
try { try {
const { appName, form } = req.body; const { id: appName } = req.params;
const { form } = req.body;
if (!appName) { if (!appName) {
throw new Error('App name is required'); throw new Error('App name is required');
} }
const appExists = fileExists(`/app-data/${appName}`); checkAppExists(appName);
if (!appExists) {
throw new Error(`App ${appName} not installed`);
}
generateEnvFile(appName, form); generateEnvFile(appName, form);
// Run script
runScript('/scripts/app.sh', ['stop', appName]);
runScript('/scripts/app.sh', ['start', appName]);
res.status(200).json({ message: 'App updated successfully' }); res.status(200).json({ message: 'App updated successfully' });
} catch (e) { } catch (e) {
res.status(500).end(e); next(e);
} }
}; };
const installedApps = (req: Request, res: Response) => { const getAppInfo = async (req: Request, res: Response<AppConfig>, next: NextFunction) => {
try { try {
const apps = readJsonFile('/state/apps.json'); const { id } = req.params;
const appNames = apps.installed.split(' ');
if (appNames.length === 0) { if (!id) {
res.status(204).json([]); throw new Error('App name is required');
} else {
res.status(200).json(appNames);
} }
const dockerContainers = await si.dockerContainers();
const configFile: AppConfig = readJsonFile(`/apps/${id}/config.json`);
const state = getStateFile();
const installed: string[] = state.installed.split(' ').filter(Boolean);
configFile.installed = installed.includes(id);
configFile.status = (dockerContainers.find((container) => container.name === `${id}`)?.state as 'running') || 'stopped';
res.status(200).json(configFile);
} catch (e) { } catch (e) {
res.status(500).end(e); next(e);
} }
}; };
const getAppInfo = (req: Request, res: Response<AppConfig>) => { const listApps = async (req: Request, res: Response, next: NextFunction) => {
try { try {
const { appName } = req.body; const apps = appNames
.map((app) => {
try {
return readJsonFile(`/apps/${app}/config.json`);
} catch {
return null;
}
})
.filter(Boolean);
const dockerContainers = await si.dockerContainers();
const state = getStateFile();
const installed: string[] = state.installed.split(' ').filter(Boolean);
apps.forEach((app) => {
app.installed = installed.includes(app.id);
app.status = dockerContainers.find((container) => container.name === `${app.id}`)?.state || 'stopped';
});
res.status(200).json(apps);
} catch (e) {
next(e);
}
};
const startApp = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id: appName } = req.params;
if (!appName) { if (!appName) {
throw new Error('App name is required'); throw new Error('App name is required');
} }
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`); checkAppExists(appName);
checkEnvFile(appName);
res.status(200).json(configFile); // Run script
await runAppScript(['start', appName]);
ensureAppState(appName, true);
res.status(200).json({ message: 'App started successfully' });
} catch (e) { } catch (e) {
res.status(500).end(e); next(e);
}
};
const installApp = async (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
const { form } = req.body;
if (!id) {
throw new Error('App name is required');
}
const appIsAvailable = appNames.includes(id);
if (!appIsAvailable) {
throw new Error(`App ${id} not available`);
}
const appExists = fileExists(`/app-data/${id}`);
if (appExists) {
await startApp(req, res, next);
} else {
const appIsValid = await checkAppRequirements(id);
if (!appIsValid) {
throw new Error(`App ${id} requirements not met`);
}
// Create app folder
createFolder(`/app-data/${id}`);
// Create env file
generateEnvFile(id, form);
ensureAppState(id, true);
// Run script
await runAppScript(['install', id]);
res.status(200).json({ message: 'App installed successfully' });
}
} catch (e) {
next(e);
}
};
const initalFormValues = (req: Request, res: Response, next: NextFunction) => {
try {
const { id } = req.params;
if (!id) {
throw new Error('App name is required');
}
res.status(200).json(getInitalFormValues(id));
} catch (e) {
next(e);
} }
}; };
@ -182,8 +220,10 @@ const AppController = {
installApp, installApp,
stopApp, stopApp,
updateAppConfig, updateAppConfig,
installedApps,
getAppInfo, getAppInfo,
listApps,
startApp,
initalFormValues,
}; };
export default AppController; export default AppController;

View file

@ -0,0 +1,101 @@
import portUsed from 'tcp-port-used';
import p from 'p-iteration';
import { AppConfig } from '../../config/types';
import { fileExists, readFile, readJsonFile, runScript, writeFile } from '../fs/fs.helpers';
import { internalIpV4 } from 'internal-ip';
export const checkAppRequirements = async (appName: string) => {
let valid = true;
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`);
if (configFile.requirements?.ports) {
await p.forEachSeries(configFile.requirements.ports, async (port: number) => {
const ip = await internalIpV4();
const used = await portUsed.check(port, ip);
if (used) valid = false;
});
}
return valid;
};
export const getEnvMap = (appName: string): Map<string, string> => {
const envFile = readFile(`/app-data/${appName}/app.env`).toString();
const envVars = envFile.split('\n');
const envVarsMap = new Map<string, string>();
envVars.forEach((envVar) => {
const [key, value] = envVar.split('=');
envVarsMap.set(key, value);
});
return envVarsMap;
};
export const checkEnvFile = (appName: string) => {
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`);
const envMap = getEnvMap(appName);
Object.keys(configFile.form_fields).forEach((key) => {
const envVar = configFile.form_fields[key].env_variable;
const envVarValue = envMap.get(envVar);
if (!envVarValue && configFile.form_fields[key].required) {
throw new Error('New info needed. App config needs to be updated');
}
});
};
export const getInitalFormValues = (appName: string): Record<string, string> => {
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`);
const envMap = getEnvMap(appName);
const formValues: Record<string, string> = {};
Object.keys(configFile.form_fields).forEach((key) => {
const envVar = configFile.form_fields[key].env_variable;
const envVarValue = envMap.get(envVar);
if (envVarValue) {
formValues[key] = envVarValue;
}
});
return formValues;
};
export const checkAppExists = (appName: string) => {
const appExists = fileExists(`/app-data/${appName}`);
if (!appExists) {
throw new Error(`App ${appName} not installed`);
}
};
export const runAppScript = (params: string[]): Promise<void> => {
return new Promise((resolve, reject) => {
runScript('/scripts/app.sh', params, (err: string) => {
if (err) {
reject(err);
}
resolve();
});
});
};
export const ensureAppState = (appName: string, installed: boolean) => {
const state = readJsonFile('/state/apps.json');
if (installed) {
if (state.installed.indexOf(appName) === -1) {
state.installed += ` ${appName}`;
writeFile('/state/apps.json', JSON.stringify(state));
}
} else {
if (state.installed.indexOf(appName) !== -1) {
state.installed = state.installed.replace(` ${appName}`, '');
writeFile('/state/apps.json', JSON.stringify(state));
}
}
};

View file

@ -3,8 +3,13 @@ import AppController from './apps.controller';
const router = Router(); const router = Router();
router.route('/install').post(AppController.installApp); router.route('/install/:id').post(AppController.installApp);
router.route('/uninstall').post(AppController.uninstallApp); router.route('/update/:id').post(AppController.updateAppConfig);
router.route('/list').get(AppController.installedApps); router.route('/uninstall/:id').get(AppController.uninstallApp);
router.route('/stop/:id').get(AppController.stopApp);
router.route('/start/:id').get(AppController.startApp);
router.route('/list').get(AppController.listApps);
router.route('/info/:id').get(AppController.getAppInfo);
router.route('/form/:id').get(AppController.initalFormValues);
export default router; export default router;

View file

@ -9,6 +9,8 @@ export const readJsonFile = (path: string): any => {
return JSON.parse(rawFile); return JSON.parse(rawFile);
}; };
export const readFile = (path: string): string => fs.readFileSync(getAbsolutePath(path)).toString();
export const fileExists = (path: string): boolean => fs.existsSync(getAbsolutePath(path)); export const fileExists = (path: string): boolean => fs.existsSync(getAbsolutePath(path));
export const writeFile = (path: string, data: any) => fs.writeFileSync(getAbsolutePath(path), data); export const writeFile = (path: string, data: any) => fs.writeFileSync(getAbsolutePath(path), data);
@ -18,4 +20,4 @@ export const deleteFolder = (path: string) => fs.rmSync(getAbsolutePath(path), {
export const copyFile = (source: string, destination: string) => fs.copyFileSync(getAbsolutePath(source), getAbsolutePath(destination)); export const copyFile = (source: string, destination: string) => fs.copyFileSync(getAbsolutePath(source), getAbsolutePath(destination));
export const runScript = (path: string, args: string[]) => childProcess.spawnSync(getAbsolutePath(path), args, {}); export const runScript = (path: string, args: string[], callback?: any) => childProcess.execFile(getAbsolutePath(path), args, {}, callback);

Some files were not shown because too many files have changed in this diff Show more