Merge pull request #129 from meienberger/feature/database
Feature/database
This commit is contained in:
commit
7f3ebb372c
178 changed files with 6869 additions and 2479 deletions
|
@ -4,3 +4,6 @@
|
|||
/.next
|
||||
node_modules
|
||||
.next
|
||||
dist/
|
||||
**/dist/
|
||||
**/next/
|
||||
|
|
29
.github/workflows/ci.yml
vendored
29
.github/workflows/ci.yml
vendored
|
@ -10,6 +10,19 @@ env:
|
|||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
ports:
|
||||
- 5432:5432
|
||||
# set health checks to wait until postgres has started
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
@ -42,15 +55,6 @@ jobs:
|
|||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages/common
|
||||
run: |
|
||||
cd ./packages/common
|
||||
npm run build
|
||||
cd ../..
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: Build packages
|
||||
run: pnpm -r build
|
||||
|
||||
|
@ -61,4 +65,9 @@ jobs:
|
|||
run: pnpm -r lint
|
||||
|
||||
- name: Run tests
|
||||
run: pnpm -r test
|
||||
run: pnpm -r test
|
||||
|
||||
- uses: codecov/codecov-action@v2
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./packages/system-api/coverage/clover.xml,./packages/dashboard/coverage/clover.xml
|
7
.github/workflows/release-candidate.yml
vendored
7
.github/workflows/release-candidate.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
|||
- name: Get tag from VERSION file
|
||||
id: meta
|
||||
run: |
|
||||
VERSION=$(cat VERSION)
|
||||
VERSION=$(npm run version --silent)
|
||||
TAG=${VERSION}
|
||||
echo "::set-output name=tag::${TAG}"
|
||||
|
||||
|
@ -40,7 +40,4 @@ jobs:
|
|||
push: true
|
||||
tags: meienberger/runtipi:rc-${{ steps.meta.outputs.TAG }}
|
||||
cache-from: type=registry,ref=meienberger/runtipi:buildcache
|
||||
cache-to: type=registry,ref=meienberger/runtipi:buildcache,mode=max
|
||||
|
||||
|
||||
|
||||
cache-to: type=registry,ref=meienberger/runtipi:buildcache,mode=max
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -52,7 +52,7 @@ jobs:
|
|||
- name: Get tag from VERSION file
|
||||
id: meta
|
||||
run: |
|
||||
VERSION=$(cat VERSION)
|
||||
VERSION=$(npm run version --silent)
|
||||
TAG=${VERSION}
|
||||
echo "::set-output name=tag::${TAG}"
|
||||
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,6 +4,7 @@ data/
|
|||
github.secrets
|
||||
node_modules/
|
||||
app-data/*
|
||||
data/
|
||||
traefik/ssl/*
|
||||
!traefik/ssl/.gitkeep
|
||||
!app-data/.gitkeep
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
|
||||
pnpm test
|
||||
pnpm -r test
|
||||
pnpm -r lint
|
||||
pnpm -r lint:fix
|
||||
|
|
62
Dockerfile
62
Dockerfile
|
@ -2,68 +2,44 @@ FROM node:18 AS build
|
|||
|
||||
RUN npm install node-gyp -g
|
||||
|
||||
WORKDIR /common
|
||||
COPY ./packages/common /common
|
||||
RUN npm i
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /api
|
||||
COPY ./packages/system-api/package.json /api/package.json
|
||||
RUN npm i
|
||||
COPY ./packages/system-api /api
|
||||
RUN npm run build
|
||||
|
||||
# ---
|
||||
WORKDIR /dashboard
|
||||
COPY ./packages/dashboard/package.json /dashboard/package.json
|
||||
RUN npm i
|
||||
|
||||
WORKDIR /api
|
||||
COPY ./packages/system-api /api
|
||||
RUN npm run build
|
||||
# ---
|
||||
WORKDIR /dashboard
|
||||
COPY ./packages/dashboard /dashboard
|
||||
RUN npm run build
|
||||
|
||||
|
||||
FROM ubuntu:20.04
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
FROM alpine:3.16.0 as app
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install docker
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
# Install dependencies
|
||||
RUN apk --no-cache add docker-compose nodejs npm bash g++ make
|
||||
|
||||
RUN apt-get install -y \
|
||||
g++ gcc make python
|
||||
|
||||
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
RUN echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
|
||||
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y docker-ce docker-ce-cli containerd.io
|
||||
|
||||
# Install node
|
||||
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
|
||||
RUN apt-get install -y nodejs
|
||||
RUN npm install node-gyp -g
|
||||
|
||||
# Install docker-compose
|
||||
RUN curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
RUN chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
COPY --from=build /common /common
|
||||
|
||||
WORKDIR /api
|
||||
COPY ./packages/system-api/package.json /api/package.json
|
||||
RUN npm install --omit=dev
|
||||
COPY ./packages/system-api/package*.json /api/
|
||||
RUN npm install --production
|
||||
|
||||
WORKDIR /dashboard
|
||||
COPY ./packages/dashboard/package.json /dashboard/package.json
|
||||
RUN npm install --omit=dev
|
||||
COPY ./packages/dashboard/package*.json /dashboard/
|
||||
RUN npm install --production
|
||||
|
||||
COPY --from=build /api /api
|
||||
COPY --from=build /dashboard /dashboard
|
||||
COPY --from=build /api/dist /api/dist
|
||||
COPY ./packages/system-api /api
|
||||
|
||||
COPY --from=build /dashboard/.next /dashboard/.next
|
||||
COPY ./packages/dashboard /dashboard
|
||||
|
||||
WORKDIR /
|
||||
|
|
|
@ -1,40 +1,18 @@
|
|||
FROM ubuntu:20.04
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
FROM alpine:3.16.0 as app
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install docker
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
RUN apk --no-cache add docker-compose nodejs npm bash
|
||||
|
||||
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
|
||||
|
||||
RUN echo \
|
||||
"deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu \
|
||||
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y docker-ce docker-ce-cli containerd.io
|
||||
|
||||
# Install node
|
||||
RUN curl -sL https://deb.nodesource.com/setup_18.x | bash -
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Install docker-compose
|
||||
RUN curl -L "https://github.com/docker/compose/releases/download/v2.5.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
RUN chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
COPY ./packages/common /common
|
||||
RUN npm install node-gyp -g
|
||||
|
||||
WORKDIR /api
|
||||
COPY ./packages/system-api/package.json /api/package.json
|
||||
COPY ./packages/system-api/package*.json /api/
|
||||
RUN npm install
|
||||
|
||||
WORKDIR /dashboard
|
||||
COPY ./packages/dashboard/package.json /dashboard/package.json
|
||||
COPY ./packages/dashboard/package*.json /dashboard/
|
||||
RUN npm install
|
||||
|
||||
COPY ./packages/system-api /api
|
||||
|
|
|
@ -12,5 +12,5 @@
|
|||
"requirements": {
|
||||
"ports": [53]
|
||||
},
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"name": "Anonaddy",
|
||||
"port": 8084,
|
||||
"available": false,
|
||||
"categories": ["utilities"],
|
||||
"id": "anonaddy",
|
||||
"description": "",
|
||||
"short_desc": "Anonymous email forwarding",
|
||||
|
@ -10,21 +12,21 @@
|
|||
"requirements": {
|
||||
"ports": [25]
|
||||
},
|
||||
"form_fields": {
|
||||
"username": {
|
||||
"form_fields": [
|
||||
{
|
||||
"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,
|
||||
|
@ -32,7 +34,7 @@
|
|||
"required": true,
|
||||
"env_variable": "ANONADDY_DOMAIN"
|
||||
},
|
||||
"secret": {
|
||||
{
|
||||
"type": "text",
|
||||
"label": "App secret",
|
||||
"hint": "Long random string used when hashing data for the anonymous replies",
|
||||
|
@ -41,5 +43,5 @@
|
|||
"required": true,
|
||||
"env_variable": "ANONADDY_SECRET"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ services:
|
|||
ANONADDY_DOMAIN: ${ANONADDY_DOMAIN}
|
||||
ANONADDY_SECRET: ${ANONADDY_SECRET}
|
||||
ANONADDY_ADMIN_USERNAME: ${ANONADDY_USERNAME}
|
||||
POSTFIX_DEBUG: true
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "https://github.com/janeczku/",
|
||||
"source": "https://github.com/janeczku/calibre-web",
|
||||
"image": "/logos/apps/calibre-web.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
"author": "https://github.com/coder",
|
||||
"source": "https://github.com/linuxserver/docker-code-server",
|
||||
"image": "https://avatars.githubusercontent.com/u/95932066",
|
||||
"form_fields": {
|
||||
"password": {
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Password",
|
||||
"max": 50,
|
||||
|
@ -18,5 +18,5 @@
|
|||
"required": true,
|
||||
"env_variable": "CODESERVER_PASSWORD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,4 +2,5 @@ version: "3.7"
|
|||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
name: runtipi_tipi_main_network
|
||||
external:
|
||||
name: runtipi_tipi_main_network
|
|
@ -10,5 +10,5 @@
|
|||
"website": "https://filebrowser.org/",
|
||||
"source": "https://github.com/filebrowser/filebrowser",
|
||||
"image": "/logos/apps/filebrowser.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -8,5 +8,5 @@
|
|||
"author": "FileRun, LDA - Portugal",
|
||||
"source": "https://www.filerun.com/",
|
||||
"image": "https://avatars.githubusercontent.com/u/6422152?v=4",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "https://freshrss.org/",
|
||||
"source": "https://github.com/FreshRSS/FreshRSS",
|
||||
"image": "/logos/apps/freshrss.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "go-gitea",
|
||||
"source": "https://github.com/go-gitea/gitea",
|
||||
"image": "/logos/apps/gitea.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
"source": "https://github.com/ajnart/homarr",
|
||||
"website": "https://discord.gg/C2WTXkzkwK",
|
||||
"image": "/logos/apps/homarr.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "ArneNaessens",
|
||||
"source": "https://github.com/home-assistant/core",
|
||||
"image": "/logos/apps/homeassistant.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "iv-org",
|
||||
"source": "https://github.com/iv-org/invidious",
|
||||
"image": "https://raw.githubusercontent.com/iv-org/invidious/master/assets/invidious-colored-vector.svg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "",
|
||||
"source": "https://github.com/Jackett/Jackett",
|
||||
"image": "/logos/apps/jackett.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "jellyfin.org",
|
||||
"source": "https://github.com/jellyfin/jellyfin",
|
||||
"image": "https://avatars.githubusercontent.com/u/45698031?s=200&v=4",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
"source": "https://github.com/laurent22/joplin",
|
||||
"website": "https://joplinapp.org",
|
||||
"image": "/logos/apps/joplin.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "spikecodes",
|
||||
"source": "https://github.com/spikecodes/libreddit",
|
||||
"image": "/logos/apps/libreddit.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"categories": [],
|
||||
"source": "https://github.com/hay-kot/mealie",
|
||||
"image": "https://raw.githubusercontent.com/hay-kot/mealie/mealie-next/frontend/static/icons/android-chrome-512x512.png",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
"source": "https://github.com/n8n-io/n8n",
|
||||
"website": "https://n8n.io/",
|
||||
"image": "/logos/apps/n8n.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
"author": "Nextcloud GmbH",
|
||||
"source": "https://github.com/nextcloud/server",
|
||||
"image": "https://avatars.githubusercontent.com/u/19211038?s=200&v=4",
|
||||
"form_fields": {
|
||||
"username": {
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Username",
|
||||
"max": 50,
|
||||
|
@ -18,7 +18,7 @@
|
|||
"required": true,
|
||||
"env_variable": "NEXTCLOUD_ADMIN_USER"
|
||||
},
|
||||
"password": {
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Password",
|
||||
"max": 50,
|
||||
|
@ -26,5 +26,5 @@
|
|||
"required": true,
|
||||
"env_variable": "NEXTCLOUD_ADMIN_PASSWORD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "zedeus",
|
||||
"source": "https://github.com/zedeus/nitter",
|
||||
"image": "/logos/apps/nitter.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "node-red",
|
||||
"source": "https://github.com/node-red/node-red",
|
||||
"image": "https://avatars.githubusercontent.com/u/5375661?s=200&v=4",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
"author": "PhotoPrism",
|
||||
"source": "https://github.com/photoprism/photoprism",
|
||||
"image": "/logos/apps/photoprism.jpg",
|
||||
"form_fields": {
|
||||
"password": {
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Photoprism admin password",
|
||||
"max": 50,
|
||||
|
@ -18,5 +18,5 @@
|
|||
"required": true,
|
||||
"env_variable": "PHOTOPRISM_ADMIN_PASSWORD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"author": "pi-hole.net",
|
||||
"source": "https://github.com/pi-hole/pi-hole",
|
||||
"image": "/logos/apps/pihole.jpg",
|
||||
"form_fields": {
|
||||
"password": {
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Password",
|
||||
"max": 50,
|
||||
|
@ -21,5 +21,5 @@
|
|||
"required": true,
|
||||
"env_variable": "APP_PASSWORD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
"author": "plexinc",
|
||||
"source": "https://github.com/plexinc/pms-docker",
|
||||
"image": "/logos/apps/plex.png",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "Prowlarr",
|
||||
"source": "https://github.com/Prowlarr/Prowlarr/",
|
||||
"image": "/logos/apps/prowlarr.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "radarr.video",
|
||||
"source": "https://github.com/Radarr/Radarr",
|
||||
"image": "/logos/apps/radarr.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "Resilio, Inc.",
|
||||
"source": "https://github.com/bt-sync",
|
||||
"image": "/logos/apps/resilio-sync.png",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -7,5 +7,5 @@
|
|||
"author": "",
|
||||
"source": "https://github.com/boypt/simple-torrent",
|
||||
"image": "https://getumbrel.github.io/umbrel-apps-gallery/simple-torrent/icon.svg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "sonarr.tv",
|
||||
"source": "https://github.com/Sonarr/Sonarr",
|
||||
"image": "/logos/apps/sonarr.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
"source": "https://github.com/syncthing",
|
||||
"website": "https://syncthing.net",
|
||||
"image": "/logos/apps/syncthing.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -10,5 +10,5 @@
|
|||
"source": "https://github.com/tailscale/tailscale",
|
||||
"website": "https://tailscale.com/",
|
||||
"image": "https://avatars.githubusercontent.com/u/48932923?s=200&v=4",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
"author": "JonnyWong16",
|
||||
"source": "https://github.com/Tautulli/Tautulli",
|
||||
"image": "/logos/apps/tautulli.jpg",
|
||||
"form_fields": {}
|
||||
"form_fields": []
|
||||
}
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
"author": "Transmission Project",
|
||||
"source": "https://github.com/transmission/transmission",
|
||||
"image": "https://avatars.githubusercontent.com/u/223312?s=200&v=4",
|
||||
"form_fields": {
|
||||
"username": {
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Username",
|
||||
"max": 50,
|
||||
|
@ -21,7 +21,7 @@
|
|||
"required": true,
|
||||
"env_variable": "TRANSMISSION_USERNAME"
|
||||
},
|
||||
"password": {
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Password",
|
||||
"max": 50,
|
||||
|
@ -29,5 +29,5 @@
|
|||
"required": true,
|
||||
"env_variable": "TRANSMISSION_PASSWORD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,29 +1,28 @@
|
|||
{
|
||||
"name": "TTYD - Web terminal",
|
||||
"port": 8092,
|
||||
"id": "ttyd",
|
||||
"description": "",
|
||||
"short_desc": "A utility that allows you to access a command line from your web browser",
|
||||
"author": "",
|
||||
"source": "",
|
||||
"image": "",
|
||||
"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"
|
||||
}
|
||||
"name": "TTYD - Web terminal",
|
||||
"port": 8092,
|
||||
"id": "ttyd",
|
||||
"description": "",
|
||||
"short_desc": "A utility that allows you to access a command line from your web browser",
|
||||
"author": "",
|
||||
"source": "",
|
||||
"image": "",
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "text",
|
||||
"label": "Username",
|
||||
"max": 50,
|
||||
"min": 3,
|
||||
"required": true,
|
||||
"env_variable": "TRANSMISSION_USERNAME"
|
||||
},
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Password",
|
||||
"max": 50,
|
||||
"min": 3,
|
||||
"required": true,
|
||||
"env_variable": "TRANSMISSION_PASSWORD"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
"author": "Daniel García",
|
||||
"source": "https://github.com/dani-garcia/vaultwarden",
|
||||
"image": "/logos/apps/vaultwarden.jpg",
|
||||
"form_fields": {
|
||||
"admin_password": {
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Admin Panel Password",
|
||||
"max": 50,
|
||||
|
@ -18,5 +18,5 @@
|
|||
"required": true,
|
||||
"env_variable": "VAULTWARDEN_ADMIN_PASSWORD"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
"author": "WeeJeWel",
|
||||
"source": "https://github.com/WeeJeWel/wg-easy/",
|
||||
"image": "/logos/apps/wireguard.jpg",
|
||||
"form_fields": {
|
||||
"host": {
|
||||
"form_fields": [
|
||||
{
|
||||
"type": "fqdnip",
|
||||
"label": "Your public IP address or domain name",
|
||||
"required": true,
|
||||
"env_variable": "WIREGUARD_HOST"
|
||||
},
|
||||
"password": {
|
||||
{
|
||||
"type": "password",
|
||||
"label": "Password",
|
||||
"max": 50,
|
||||
|
@ -27,11 +27,11 @@
|
|||
"required": true,
|
||||
"env_variable": "WIREGUARD_PASSWORD"
|
||||
},
|
||||
"dns": {
|
||||
{
|
||||
"type": "ip",
|
||||
"label": "Default DNS server",
|
||||
"required": false,
|
||||
"env_variable": "WIREGUARD_DNS"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,11 +1,35 @@
|
|||
version: "3.7"
|
||||
|
||||
services:
|
||||
tipi-db:
|
||||
container_name: tipi-db
|
||||
image: postgres:latest
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_USER: tipi
|
||||
POSTGRES_DB: tipi
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -d tipi -U tipi" ]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 120
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
api:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
command: bash -c "cd /api && npm run dev"
|
||||
command: /bin/sh -c "cd /api && npm run build && npm run dev"
|
||||
depends_on:
|
||||
tipi-db:
|
||||
condition: service_healthy
|
||||
container_name: api
|
||||
ports:
|
||||
- 3001:3001
|
||||
|
@ -13,14 +37,18 @@ services:
|
|||
## Docker sock
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ${PWD}:/tipi
|
||||
- ${PWD}/packages/system-api:/api
|
||||
- /api/node_modules
|
||||
- ${PWD}/packages/system-api/src:/api/src
|
||||
# - /api/node_modules
|
||||
environment:
|
||||
- INTERNAL_IP=${INTERNAL_IP}
|
||||
- TIPI_VERSION=${TIPI_VERSION}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- ROOT_FOLDER_HOST=${ROOT_FOLDER_HOST}
|
||||
- NGINX_PORT=${NGINX_PORT}
|
||||
INTERNAL_IP: ${INTERNAL_IP}
|
||||
TIPI_VERSION: ${TIPI_VERSION}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
ROOT_FOLDER_HOST: ${ROOT_FOLDER_HOST}
|
||||
NGINX_PORT: ${NGINX_PORT}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_USERNAME: tipi
|
||||
POSTGRES_DBNAME: tipi
|
||||
POSTGRES_HOST: tipi-db
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
|
@ -28,7 +56,7 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
command: bash -c "cd /dashboard && npm run dev"
|
||||
command: /bin/sh -c "cd /dashboard && npm run dev"
|
||||
container_name: dashboard
|
||||
ports:
|
||||
- 3000:3000
|
||||
|
@ -37,9 +65,9 @@ services:
|
|||
environment:
|
||||
- INTERNAL_IP=${INTERNAL_IP}
|
||||
volumes:
|
||||
- ${PWD}/packages/dashboard:/dashboard
|
||||
- /dashboard/node_modules
|
||||
- /dashboard/.next
|
||||
- ${PWD}/packages/dashboard/src:/dashboard/src
|
||||
# - /dashboard/node_modules
|
||||
# - /dashboard/.next
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/") # Host(`tipi.local`) &&
|
||||
|
@ -50,9 +78,6 @@ services:
|
|||
networks:
|
||||
tipi_main_network:
|
||||
driver: bridge
|
||||
driver_opts:
|
||||
com.docker.network.bridge.enable_ip_masquerade: "true"
|
||||
com.docker.network.bridge.enable_icc: "true"
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
|
|
|
@ -15,6 +15,27 @@ services:
|
|||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
tipi-db:
|
||||
container_name: tipi-db
|
||||
image: postgres:latest
|
||||
restart: on-failure
|
||||
stop_grace_period: 1m
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_USER: tipi
|
||||
POSTGRES_DB: tipi
|
||||
healthcheck:
|
||||
test: [ "CMD-SHELL", "pg_isready -d tipi -U tipi" ]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 120
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
api:
|
||||
image: meienberger/runtipi:${TIPI_VERSION}
|
||||
command: bash -c "cd /api && npm run start"
|
||||
|
@ -22,16 +43,23 @@ services:
|
|||
container_name: api
|
||||
ports:
|
||||
- 3001:3001
|
||||
depends_on:
|
||||
tipi-db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
## Docker sock
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ${PWD}:/tipi
|
||||
environment:
|
||||
- INTERNAL_IP=${INTERNAL_IP}
|
||||
- TIPI_VERSION=${TIPI_VERSION}
|
||||
- JWT_SECRET=${JWT_SECRET}
|
||||
- ROOT_FOLDER_HOST=${ROOT_FOLDER_HOST}
|
||||
- NGINX_PORT=${NGINX_PORT}
|
||||
INTERNAL_IP: ${INTERNAL_IP}
|
||||
TIPI_VERSION: ${TIPI_VERSION}
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
ROOT_FOLDER_HOST: ${ROOT_FOLDER_HOST}
|
||||
NGINX_PORT: ${NGINX_PORT}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
POSTGRES_USERNAME: tipi
|
||||
POSTGRES_DBNAME: tipi
|
||||
POSTGRES_HOST: tipi-db
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
|
|
|
@ -10,7 +10,9 @@
|
|||
"start:dev": "docker-compose -f docker-compose.dev.yml --env-file .env.dev up --build",
|
||||
"start:rc": "docker-compose -f docker-compose.rc.yml --env-file .env up --build",
|
||||
"start:prod": "docker-compose --env-file .env up --build",
|
||||
"build:common": "cd packages/common && npm run build"
|
||||
"build:common": "cd packages/common && npm run build",
|
||||
"start:pg": "docker run --name test-db -p 5432:5432 -d --rm -e POSTGRES_PASSWORD=postgres postgres",
|
||||
"version": "echo $npm_package_version"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.5.0",
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
node_modules/
|
||||
dist/
|
||||
*.cjs
|
||||
dist/
|
|
@ -1,18 +0,0 @@
|
|||
module.exports = {
|
||||
env: { node: true, jest: true },
|
||||
extends: ['airbnb-typescript', 'eslint:recommended', 'plugin:import/typescript'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import', 'react'],
|
||||
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' }],
|
||||
},
|
||||
};
|
3
packages/common/.gitignore
vendored
3
packages/common/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
node_modules/
|
||||
dist/
|
||||
coverage/
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
module.exports = {
|
||||
singleQuote: true,
|
||||
semi: true,
|
||||
trailingComma: 'all',
|
||||
printWidth: 200,
|
||||
};
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "@runtipi/common",
|
||||
"version": "0.2.8",
|
||||
"main": "./dist/index.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "jest --coverage --passWithNoTests",
|
||||
"build": "tsc -b tsconfig.build.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.14.38",
|
||||
"typescript": "4.6.4"
|
||||
},
|
||||
"dependencies": {},
|
||||
"description": "",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { AppCategoriesEnum } from '../types';
|
||||
|
||||
// Icons should come from FontAwesome https://react-icons.github.io/react-icons/icons?name=fa
|
||||
export const APP_CATEGORIES = [
|
||||
{ name: 'Network', id: AppCategoriesEnum.NETWORK, icon: 'FaNetworkWired' },
|
||||
{ name: 'Media', id: AppCategoriesEnum.MEDIA, icon: 'FaVideo' },
|
||||
{ name: 'Development', id: AppCategoriesEnum.DEVELOPMENT, icon: 'FaCode' },
|
||||
{ name: 'Automation', id: AppCategoriesEnum.AUTOMATION, icon: 'FaRobot' },
|
||||
{ name: 'Social', id: AppCategoriesEnum.SOCIAL, icon: 'FaUserFriends' },
|
||||
{ name: 'Utilities', id: AppCategoriesEnum.UTILITIES, icon: 'FaWrench' },
|
||||
{ name: 'Photography', id: AppCategoriesEnum.PHOTOGRAPHY, icon: 'FaCamera' },
|
||||
{ name: 'Security', id: AppCategoriesEnum.SECURITY, icon: 'FaShieldAlt' },
|
||||
{ name: 'Featured', id: AppCategoriesEnum.FEATURED, icon: 'FaStar' },
|
||||
{ name: 'Books', id: AppCategoriesEnum.BOOKS, icon: 'FaBook' },
|
||||
{ name: 'Data', id: AppCategoriesEnum.DATA, icon: 'FaDatabase' },
|
||||
];
|
|
@ -1 +0,0 @@
|
|||
export * from './app.constants';
|
|
@ -1,2 +0,0 @@
|
|||
export * from './types';
|
||||
export * from './constants';
|
|
@ -1,64 +0,0 @@
|
|||
export enum AppCategoriesEnum {
|
||||
NETWORK = 'network',
|
||||
MEDIA = 'media',
|
||||
DEVELOPMENT = 'development',
|
||||
AUTOMATION = 'automation',
|
||||
SOCIAL = 'social',
|
||||
UTILITIES = 'utilities',
|
||||
PHOTOGRAPHY = 'photography',
|
||||
SECURITY = 'security',
|
||||
FEATURED = 'featured',
|
||||
BOOKS = 'books',
|
||||
DATA = 'data',
|
||||
}
|
||||
|
||||
export enum FieldTypes {
|
||||
text = 'text',
|
||||
password = 'password',
|
||||
email = 'email',
|
||||
number = 'number',
|
||||
fqdn = 'fqdn',
|
||||
ip = 'ip',
|
||||
fqdnip = 'fqdnip',
|
||||
url = 'url',
|
||||
}
|
||||
|
||||
interface FormField {
|
||||
type: FieldTypes;
|
||||
label: string;
|
||||
max?: number;
|
||||
min?: number;
|
||||
hint?: string;
|
||||
required?: boolean;
|
||||
env_variable: string;
|
||||
}
|
||||
|
||||
export enum AppStatusEnum {
|
||||
RUNNING = 'running',
|
||||
STOPPED = 'stopped',
|
||||
INSTALLING = 'installing',
|
||||
UNINSTALLING = 'uninstalling',
|
||||
STOPPING = 'stopping',
|
||||
STARTING = 'starting',
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
id: string;
|
||||
available: boolean;
|
||||
port: number;
|
||||
name: string;
|
||||
requirements?: {
|
||||
ports?: number[];
|
||||
};
|
||||
description: string;
|
||||
version: string;
|
||||
image: string;
|
||||
form_fields: Record<string, FormField>;
|
||||
short_desc: string;
|
||||
author: string;
|
||||
source: string;
|
||||
installed: boolean;
|
||||
categories: AppCategoriesEnum[];
|
||||
status: AppStatusEnum;
|
||||
url_suffix: string;
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export * from './app.types';
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": false,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": false,
|
||||
"jsx": "preserve",
|
||||
"incremental": false,
|
||||
"declaration": true,
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.cjs", "jest.config.cjs"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -1,2 +1,5 @@
|
|||
node_modules/
|
||||
.next/
|
||||
dist/
|
||||
sessions/
|
||||
logs/
|
||||
|
|
9
packages/dashboard/codegen.yml
Normal file
9
packages/dashboard/codegen.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
overwrite: true
|
||||
schema: "http://localhost:3001/graphql"
|
||||
documents: "src/graphql/**/*.graphql"
|
||||
generates:
|
||||
src/generated/graphql.tsx:
|
||||
plugins:
|
||||
- "typescript"
|
||||
- "typescript-operations"
|
||||
- "typescript-react-apollo"
|
|
@ -7,6 +7,6 @@ module.exports = {
|
|||
// setupFiles: ['<rootDir>/tests/dotenv-config.ts'],
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.{ts,tsx}'],
|
||||
coverageProvider: 'v8',
|
||||
// coverageProvider: 'v8',
|
||||
passWithNoTests: true,
|
||||
};
|
||||
|
|
|
@ -7,39 +7,46 @@
|
|||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "next lint",
|
||||
"lint:fix": "next lint --fix",
|
||||
"gen": "graphql-codegen --config codegen.yml"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.6.8",
|
||||
"@chakra-ui/react": "^2.1.2",
|
||||
"@emotion/react": "^11",
|
||||
"@emotion/styled": "^11",
|
||||
"@fontsource/open-sans": "^4.5.8",
|
||||
"@runtipi/common": "file:../common",
|
||||
"axios": "^0.26.1",
|
||||
"clsx": "^1.1.1",
|
||||
"final-form": "^4.20.6",
|
||||
"framer-motion": "^6",
|
||||
"graphql": "^15.8.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"immer": "^9.0.12",
|
||||
"js-cookie": "^3.0.1",
|
||||
"next": "12.1.6",
|
||||
"npm": "^8.12.1",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-final-form": "^6.5.9",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-select": "^5.3.2",
|
||||
"react-syntax-highlighter": "^15.5.0",
|
||||
"remark-breaks": "^3.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-mdx": "^2.1.1",
|
||||
"swr": "^1.3.0",
|
||||
"systeminformation": "^5.11.9",
|
||||
"tslib": "^2.4.0",
|
||||
"validator": "^13.7.0",
|
||||
"zustand": "^3.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@graphql-codegen/cli": "^2.6.2",
|
||||
"@graphql-codegen/typescript": "^2.5.1",
|
||||
"@graphql-codegen/typescript-operations": "^2.4.2",
|
||||
"@graphql-codegen/typescript-react-apollo": "^3.2.16",
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@types/node": "17.0.31",
|
||||
"@types/react": "18.0.8",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { FiPauseCircle, FiPlayCircle } from 'react-icons/fi';
|
||||
import { AppStatusEnum } from '@runtipi/common';
|
||||
import { AppStatusEnum } from '../../generated/graphql';
|
||||
|
||||
const AppStatus: React.FC<{ status: AppStatusEnum }> = ({ status }) => {
|
||||
if (status === AppStatusEnum.RUNNING) {
|
||||
if (status === AppStatusEnum.Running) {
|
||||
return (
|
||||
<>
|
||||
<FiPlayCircle className="text-green-500 mr-1" size={20} />
|
||||
|
|
|
@ -2,12 +2,14 @@ import { Box, SlideFade, useColorModeValue } from '@chakra-ui/react';
|
|||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import { FiChevronRight } from 'react-icons/fi';
|
||||
import { AppConfig } from '@runtipi/common';
|
||||
import AppStatus from './AppStatus';
|
||||
import AppLogo from '../AppLogo/AppLogo';
|
||||
import { limitText } from '../../modules/AppStore/helpers/table.helpers';
|
||||
import { AppInfo, AppStatusEnum } from '../../generated/graphql';
|
||||
|
||||
const AppTile: React.FC<{ app: AppConfig }> = ({ app }) => {
|
||||
type AppTileInfo = Pick<AppInfo, 'id' | 'name' | 'description' | 'image' | 'short_desc'>;
|
||||
|
||||
const AppTile: React.FC<{ app: AppTileInfo; status: AppStatusEnum }> = ({ app, status }) => {
|
||||
const bg = useColorModeValue('white', '#1a202c');
|
||||
|
||||
return (
|
||||
|
@ -18,11 +20,9 @@ const AppTile: React.FC<{ app: AppConfig }> = ({ app }) => {
|
|||
<div className="mr-3 flex-1">
|
||||
<h3 className="font-bold text-xl">{app.name}</h3>
|
||||
<span>{limitText(app.short_desc, 50)}</span>
|
||||
{app.installed && (
|
||||
<div className="flex mt-1">
|
||||
<AppStatus status={app.status} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex mt-1">
|
||||
<AppStatus status={status} />
|
||||
</div>
|
||||
</div>
|
||||
<FiChevronRight className="text-slate-300" size={30} />
|
||||
</Box>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import validator from 'validator';
|
||||
import { AppConfig, FieldTypes } from '@runtipi/common';
|
||||
import { FieldTypesEnum, FormField } from '../../generated/graphql';
|
||||
|
||||
const validateField = (field: AppConfig['form_fields'][0], value: string): string | undefined => {
|
||||
const validateField = (field: FormField, value: string): string | undefined => {
|
||||
if (field.required && !value) {
|
||||
return `${field.label} is required`;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ const validateField = (field: AppConfig['form_fields'][0], value: string): strin
|
|||
}
|
||||
|
||||
switch (field.type) {
|
||||
case FieldTypes.text:
|
||||
case FieldTypesEnum.Text:
|
||||
if (field.max && value.length > field.max) {
|
||||
return `${field.label} must be less than ${field.max} characters`;
|
||||
}
|
||||
|
@ -19,37 +19,37 @@ const validateField = (field: AppConfig['form_fields'][0], value: string): strin
|
|||
return `${field.label} must be at least ${field.min} characters`;
|
||||
}
|
||||
break;
|
||||
case FieldTypes.password:
|
||||
if (!validator.isLength(value, { min: field.min, max: field.max })) {
|
||||
case FieldTypesEnum.Password:
|
||||
if (!validator.isLength(value, { min: field.min || 0, max: field.max || 100 })) {
|
||||
return `${field.label} must be between ${field.min} and ${field.max} characters`;
|
||||
}
|
||||
break;
|
||||
case FieldTypes.email:
|
||||
case FieldTypesEnum.Email:
|
||||
if (!validator.isEmail(value)) {
|
||||
return `${field.label} must be a valid email address`;
|
||||
}
|
||||
break;
|
||||
case FieldTypes.number:
|
||||
case FieldTypesEnum.Number:
|
||||
if (!validator.isNumeric(value)) {
|
||||
return `${field.label} must be a number`;
|
||||
}
|
||||
break;
|
||||
case FieldTypes.fqdn:
|
||||
case FieldTypesEnum.Fqdn:
|
||||
if (!validator.isFQDN(value)) {
|
||||
return `${field.label} must be a valid domain`;
|
||||
}
|
||||
break;
|
||||
case FieldTypes.ip:
|
||||
case FieldTypesEnum.Ip:
|
||||
if (!validator.isIP(value)) {
|
||||
return `${field.label} must be a valid IP address`;
|
||||
}
|
||||
break;
|
||||
case FieldTypes.fqdnip:
|
||||
case FieldTypesEnum.Fqdnip:
|
||||
if (!validator.isFQDN(value || '') && !validator.isIP(value)) {
|
||||
return `${field.label} must be a valid domain or IP address`;
|
||||
}
|
||||
break;
|
||||
case FieldTypes.url:
|
||||
case FieldTypesEnum.Url:
|
||||
if (!validator.isURL(value)) {
|
||||
return `${field.label} must be a valid URL`;
|
||||
}
|
||||
|
@ -59,11 +59,11 @@ const validateField = (field: AppConfig['form_fields'][0], value: string): strin
|
|||
}
|
||||
};
|
||||
|
||||
export const validateAppConfig = (values: Record<string, string>, fields: (AppConfig['form_fields'][0] & { id: string })[]) => {
|
||||
export const validateAppConfig = (values: Record<string, string>, fields: FormField[]) => {
|
||||
const errors: any = {};
|
||||
|
||||
fields.forEach((field) => {
|
||||
errors[field.id] = validateField(field, values[field.id]);
|
||||
errors[field.env_variable] = validateField(field, values[field.env_variable]);
|
||||
});
|
||||
|
||||
return errors;
|
||||
|
|
|
@ -8,12 +8,12 @@ import Link from 'next/link';
|
|||
import clsx from 'clsx';
|
||||
import { useRouter } from 'next/router';
|
||||
import { IconType } from 'react-icons';
|
||||
import { useAuthStore } from '../../state/authStore';
|
||||
import { useLogoutMutation } from '../../generated/graphql';
|
||||
|
||||
const SideMenu: React.FC = () => {
|
||||
const router = useRouter();
|
||||
const { colorMode, setColorMode } = useColorMode();
|
||||
const { logout } = useAuthStore();
|
||||
const [logout] = useLogoutMutation({ refetchQueries: ['Me'] });
|
||||
const path = router.pathname.split('/')[1];
|
||||
|
||||
const renderMenuItem = (title: string, name: string, Icon: IconType) => {
|
||||
|
@ -53,7 +53,7 @@ const SideMenu: React.FC = () => {
|
|||
<Flex flex="1" />
|
||||
<List>
|
||||
<div className="mx-3">
|
||||
<ListItem onClick={logout} className="cursor-pointer hover:font-bold flex items-center mb-5">
|
||||
<ListItem onClick={() => logout()} className="cursor-pointer hover:font-bold flex items-center mb-5">
|
||||
<FiLogOut size={20} className="mr-3" />
|
||||
<p className="flex-1">Log out</p>
|
||||
</ListItem>
|
||||
|
|
11
packages/dashboard/src/core/apollo/client.ts
Normal file
11
packages/dashboard/src/core/apollo/client.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { ApolloClient, from, InMemoryCache } from '@apollo/client';
|
||||
import links from './links';
|
||||
|
||||
export const createApolloClient = async (ip: string): Promise<ApolloClient<any>> => {
|
||||
const additiveLink = from([links.errorLink, links.httpLink(ip)]);
|
||||
|
||||
return new ApolloClient({
|
||||
link: additiveLink,
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
};
|
14
packages/dashboard/src/core/apollo/links/errorLink.ts
Normal file
14
packages/dashboard/src/core/apollo/links/errorLink.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { onError } from '@apollo/client/link/error';
|
||||
|
||||
const errorLink = onError(({ graphQLErrors, networkError }) => {
|
||||
if (graphQLErrors)
|
||||
graphQLErrors.forEach(({ message, locations, path }) => {
|
||||
console.warn(`Error link [GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`);
|
||||
});
|
||||
|
||||
if (networkError) {
|
||||
console.warn(`Error link [Network error]: ${networkError}`);
|
||||
}
|
||||
});
|
||||
|
||||
export default errorLink;
|
9
packages/dashboard/src/core/apollo/links/httpLink.ts
Normal file
9
packages/dashboard/src/core/apollo/links/httpLink.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { HttpLink } from '@apollo/client';
|
||||
|
||||
const httpLink = (ip: string) =>
|
||||
new HttpLink({
|
||||
uri: `http://${ip}:3001/graphql`,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
export default httpLink;
|
9
packages/dashboard/src/core/apollo/links/index.ts
Normal file
9
packages/dashboard/src/core/apollo/links/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import errorLink from './errorLink';
|
||||
import httpLink from './httpLink';
|
||||
|
||||
const links = {
|
||||
errorLink,
|
||||
httpLink,
|
||||
};
|
||||
|
||||
export default links;
|
15
packages/dashboard/src/core/constants.ts
Normal file
15
packages/dashboard/src/core/constants.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { AppCategoriesEnum } from '../generated/graphql';
|
||||
|
||||
export const APP_CATEGORIES = [
|
||||
{ name: 'Network', id: AppCategoriesEnum.Network, icon: 'FaNetworkWired' },
|
||||
{ name: 'Media', id: AppCategoriesEnum.Media, icon: 'FaVideo' },
|
||||
{ name: 'Development', id: AppCategoriesEnum.Development, icon: 'FaCode' },
|
||||
{ name: 'Automation', id: AppCategoriesEnum.Automation, icon: 'FaRobot' },
|
||||
{ name: 'Social', id: AppCategoriesEnum.Social, icon: 'FaUserFriends' },
|
||||
{ name: 'Utilities', id: AppCategoriesEnum.Utilities, icon: 'FaWrench' },
|
||||
{ name: 'Photography', id: AppCategoriesEnum.Photography, icon: 'FaCamera' },
|
||||
{ name: 'Security', id: AppCategoriesEnum.Security, icon: 'FaShieldAlt' },
|
||||
{ name: 'Featured', id: AppCategoriesEnum.Featured, icon: 'FaStar' },
|
||||
{ name: 'Books', id: AppCategoriesEnum.Books, icon: 'FaBook' },
|
||||
{ name: 'Data', id: AppCategoriesEnum.Data, icon: 'FaDatabase' },
|
||||
];
|
920
packages/dashboard/src/generated/graphql.tsx
Normal file
920
packages/dashboard/src/generated/graphql.tsx
Normal file
|
@ -0,0 +1,920 @@
|
|||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
|
||||
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
|
||||
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
|
||||
const defaultOptions = {} as const;
|
||||
/** All built-in and custom scalars, mapped to their actual values */
|
||||
export type Scalars = {
|
||||
ID: string;
|
||||
String: string;
|
||||
Boolean: boolean;
|
||||
Int: number;
|
||||
Float: number;
|
||||
/** The javascript `Date` as string. Type represents date and time as the ISO Date string. */
|
||||
DateTime: any;
|
||||
/** The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
|
||||
JSONObject: any;
|
||||
};
|
||||
|
||||
export type App = {
|
||||
__typename?: 'App';
|
||||
config: Scalars['JSONObject'];
|
||||
createdAt: Scalars['DateTime'];
|
||||
id: Scalars['String'];
|
||||
info: AppInfo;
|
||||
lastOpened: Scalars['DateTime'];
|
||||
numOpened: Scalars['Float'];
|
||||
status: AppStatusEnum;
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export enum AppCategoriesEnum {
|
||||
Automation = 'AUTOMATION',
|
||||
Books = 'BOOKS',
|
||||
Data = 'DATA',
|
||||
Development = 'DEVELOPMENT',
|
||||
Featured = 'FEATURED',
|
||||
Media = 'MEDIA',
|
||||
Network = 'NETWORK',
|
||||
Photography = 'PHOTOGRAPHY',
|
||||
Security = 'SECURITY',
|
||||
Social = 'SOCIAL',
|
||||
Utilities = 'UTILITIES',
|
||||
}
|
||||
|
||||
export type AppInfo = {
|
||||
__typename?: 'AppInfo';
|
||||
author: Scalars['String'];
|
||||
available: Scalars['Boolean'];
|
||||
categories: Array<AppCategoriesEnum>;
|
||||
description: Scalars['String'];
|
||||
form_fields: Array<FormField>;
|
||||
id: Scalars['String'];
|
||||
image: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
port: Scalars['Float'];
|
||||
requirements?: Maybe<Scalars['JSONObject']>;
|
||||
short_desc: Scalars['String'];
|
||||
source: Scalars['String'];
|
||||
url_suffix?: Maybe<Scalars['String']>;
|
||||
version?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type AppInputType = {
|
||||
form: Scalars['JSONObject'];
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export enum AppStatusEnum {
|
||||
Installing = 'INSTALLING',
|
||||
Missing = 'MISSING',
|
||||
Running = 'RUNNING',
|
||||
Starting = 'STARTING',
|
||||
Stopped = 'STOPPED',
|
||||
Stopping = 'STOPPING',
|
||||
Uninstalling = 'UNINSTALLING',
|
||||
}
|
||||
|
||||
export type Cpu = {
|
||||
__typename?: 'Cpu';
|
||||
load: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type DiskMemory = {
|
||||
__typename?: 'DiskMemory';
|
||||
available: Scalars['Float'];
|
||||
total: Scalars['Float'];
|
||||
used: Scalars['Float'];
|
||||
};
|
||||
|
||||
export enum FieldTypesEnum {
|
||||
Email = 'email',
|
||||
Fqdn = 'fqdn',
|
||||
Fqdnip = 'fqdnip',
|
||||
Ip = 'ip',
|
||||
Number = 'number',
|
||||
Password = 'password',
|
||||
Text = 'text',
|
||||
Url = 'url',
|
||||
}
|
||||
|
||||
export type FormField = {
|
||||
__typename?: 'FormField';
|
||||
env_variable: Scalars['String'];
|
||||
hint?: Maybe<Scalars['String']>;
|
||||
label: Scalars['String'];
|
||||
max?: Maybe<Scalars['Float']>;
|
||||
min?: Maybe<Scalars['Float']>;
|
||||
required?: Maybe<Scalars['Boolean']>;
|
||||
type: FieldTypesEnum;
|
||||
};
|
||||
|
||||
export type ListAppsResonse = {
|
||||
__typename?: 'ListAppsResonse';
|
||||
apps: Array<AppInfo>;
|
||||
total: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type Mutation = {
|
||||
__typename?: 'Mutation';
|
||||
installApp: App;
|
||||
login: UserResponse;
|
||||
logout: Scalars['Boolean'];
|
||||
register: UserResponse;
|
||||
startApp: App;
|
||||
stopApp: App;
|
||||
uninstallApp: App;
|
||||
updateAppConfig: App;
|
||||
};
|
||||
|
||||
export type MutationInstallAppArgs = {
|
||||
input: AppInputType;
|
||||
};
|
||||
|
||||
export type MutationLoginArgs = {
|
||||
input: UsernamePasswordInput;
|
||||
};
|
||||
|
||||
export type MutationRegisterArgs = {
|
||||
input: UsernamePasswordInput;
|
||||
};
|
||||
|
||||
export type MutationStartAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type MutationStopAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type MutationUninstallAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type MutationUpdateAppConfigArgs = {
|
||||
input: AppInputType;
|
||||
};
|
||||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
getApp: App;
|
||||
installedApps: Array<App>;
|
||||
isConfigured: Scalars['Boolean'];
|
||||
listAppsInfo: ListAppsResonse;
|
||||
me?: Maybe<User>;
|
||||
systemInfo?: Maybe<SystemInfoResponse>;
|
||||
version: VersionResponse;
|
||||
};
|
||||
|
||||
export type QueryGetAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type SystemInfoResponse = {
|
||||
__typename?: 'SystemInfoResponse';
|
||||
cpu: Cpu;
|
||||
disk: DiskMemory;
|
||||
memory: DiskMemory;
|
||||
};
|
||||
|
||||
export type User = {
|
||||
__typename?: 'User';
|
||||
createdAt: Scalars['DateTime'];
|
||||
id: Scalars['ID'];
|
||||
updatedAt: Scalars['DateTime'];
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type UserResponse = {
|
||||
__typename?: 'UserResponse';
|
||||
user?: Maybe<User>;
|
||||
};
|
||||
|
||||
export type UsernamePasswordInput = {
|
||||
password: Scalars['String'];
|
||||
username: Scalars['String'];
|
||||
};
|
||||
|
||||
export type VersionResponse = {
|
||||
__typename?: 'VersionResponse';
|
||||
current: Scalars['String'];
|
||||
latest?: Maybe<Scalars['String']>;
|
||||
};
|
||||
|
||||
export type InstallAppMutationVariables = Exact<{
|
||||
input: AppInputType;
|
||||
}>;
|
||||
|
||||
export type InstallAppMutation = { __typename?: 'Mutation'; installApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type LoginMutationVariables = Exact<{
|
||||
input: UsernamePasswordInput;
|
||||
}>;
|
||||
|
||||
export type LoginMutation = { __typename?: 'Mutation'; login: { __typename?: 'UserResponse'; user?: { __typename?: 'User'; id: string } | null } };
|
||||
|
||||
export type LogoutMutationVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type LogoutMutation = { __typename?: 'Mutation'; logout: boolean };
|
||||
|
||||
export type RegisterMutationVariables = Exact<{
|
||||
input: UsernamePasswordInput;
|
||||
}>;
|
||||
|
||||
export type RegisterMutation = { __typename?: 'Mutation'; register: { __typename?: 'UserResponse'; user?: { __typename?: 'User'; id: string } | null } };
|
||||
|
||||
export type StartAppMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type StartAppMutation = { __typename?: 'Mutation'; startApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type StopAppMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type StopAppMutation = { __typename?: 'Mutation'; stopApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type UninstallAppMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type UninstallAppMutation = { __typename?: 'Mutation'; uninstallApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type UpdateAppConfigMutationVariables = Exact<{
|
||||
input: AppInputType;
|
||||
}>;
|
||||
|
||||
export type UpdateAppConfigMutation = { __typename?: 'Mutation'; updateAppConfig: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type GetAppQueryVariables = Exact<{
|
||||
appId: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type GetAppQuery = {
|
||||
__typename?: 'Query';
|
||||
getApp: {
|
||||
__typename?: 'App';
|
||||
id: string;
|
||||
status: AppStatusEnum;
|
||||
config: any;
|
||||
info: {
|
||||
__typename?: 'AppInfo';
|
||||
id: string;
|
||||
port: number;
|
||||
name: string;
|
||||
description: string;
|
||||
available: boolean;
|
||||
version?: string | null;
|
||||
image: string;
|
||||
short_desc: string;
|
||||
author: string;
|
||||
source: string;
|
||||
categories: Array<AppCategoriesEnum>;
|
||||
url_suffix?: string | null;
|
||||
form_fields: Array<{
|
||||
__typename?: 'FormField';
|
||||
type: FieldTypesEnum;
|
||||
label: string;
|
||||
max?: number | null;
|
||||
min?: number | null;
|
||||
hint?: string | null;
|
||||
required?: boolean | null;
|
||||
env_variable: string;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
export type InstalledAppsQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type InstalledAppsQuery = {
|
||||
__typename?: 'Query';
|
||||
installedApps: Array<{
|
||||
__typename?: 'App';
|
||||
id: string;
|
||||
status: AppStatusEnum;
|
||||
config: any;
|
||||
info: { __typename?: 'AppInfo'; id: string; name: string; description: string; image: string; short_desc: string };
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ConfiguredQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type ConfiguredQuery = { __typename?: 'Query'; isConfigured: boolean };
|
||||
|
||||
export type ListAppsQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type ListAppsQuery = {
|
||||
__typename?: 'Query';
|
||||
listAppsInfo: {
|
||||
__typename?: 'ListAppsResonse';
|
||||
total: number;
|
||||
apps: Array<{
|
||||
__typename?: 'AppInfo';
|
||||
id: string;
|
||||
available: boolean;
|
||||
image: string;
|
||||
port: number;
|
||||
name: string;
|
||||
version?: string | null;
|
||||
short_desc: string;
|
||||
author: string;
|
||||
categories: Array<AppCategoriesEnum>;
|
||||
}>;
|
||||
};
|
||||
};
|
||||
|
||||
export type MeQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type MeQuery = { __typename?: 'Query'; me?: { __typename?: 'User'; id: string } | null };
|
||||
|
||||
export type SystemInfoQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type SystemInfoQuery = {
|
||||
__typename?: 'Query';
|
||||
systemInfo?: {
|
||||
__typename?: 'SystemInfoResponse';
|
||||
cpu: { __typename?: 'Cpu'; load: number };
|
||||
disk: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
|
||||
memory: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type VersionQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type VersionQuery = { __typename?: 'Query'; version: { __typename?: 'VersionResponse'; current: string; latest?: string | null } };
|
||||
|
||||
export const InstallAppDocument = gql`
|
||||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type InstallAppMutationFn = Apollo.MutationFunction<InstallAppMutation, InstallAppMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useInstallAppMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useInstallAppMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useInstallAppMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [installAppMutation, { data, loading, error }] = useInstallAppMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useInstallAppMutation(baseOptions?: Apollo.MutationHookOptions<InstallAppMutation, InstallAppMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<InstallAppMutation, InstallAppMutationVariables>(InstallAppDocument, options);
|
||||
}
|
||||
export type InstallAppMutationHookResult = ReturnType<typeof useInstallAppMutation>;
|
||||
export type InstallAppMutationResult = Apollo.MutationResult<InstallAppMutation>;
|
||||
export type InstallAppMutationOptions = Apollo.BaseMutationOptions<InstallAppMutation, InstallAppMutationVariables>;
|
||||
export const LoginDocument = gql`
|
||||
mutation Login($input: UsernamePasswordInput!) {
|
||||
login(input: $input) {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type LoginMutationFn = Apollo.MutationFunction<LoginMutation, LoginMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useLoginMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useLoginMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useLoginMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [loginMutation, { data, loading, error }] = useLoginMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useLoginMutation(baseOptions?: Apollo.MutationHookOptions<LoginMutation, LoginMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<LoginMutation, LoginMutationVariables>(LoginDocument, options);
|
||||
}
|
||||
export type LoginMutationHookResult = ReturnType<typeof useLoginMutation>;
|
||||
export type LoginMutationResult = Apollo.MutationResult<LoginMutation>;
|
||||
export type LoginMutationOptions = Apollo.BaseMutationOptions<LoginMutation, LoginMutationVariables>;
|
||||
export const LogoutDocument = gql`
|
||||
mutation Logout {
|
||||
logout
|
||||
}
|
||||
`;
|
||||
export type LogoutMutationFn = Apollo.MutationFunction<LogoutMutation, LogoutMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useLogoutMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useLogoutMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useLogoutMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [logoutMutation, { data, loading, error }] = useLogoutMutation({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useLogoutMutation(baseOptions?: Apollo.MutationHookOptions<LogoutMutation, LogoutMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<LogoutMutation, LogoutMutationVariables>(LogoutDocument, options);
|
||||
}
|
||||
export type LogoutMutationHookResult = ReturnType<typeof useLogoutMutation>;
|
||||
export type LogoutMutationResult = Apollo.MutationResult<LogoutMutation>;
|
||||
export type LogoutMutationOptions = Apollo.BaseMutationOptions<LogoutMutation, LogoutMutationVariables>;
|
||||
export const RegisterDocument = gql`
|
||||
mutation Register($input: UsernamePasswordInput!) {
|
||||
register(input: $input) {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type RegisterMutationFn = Apollo.MutationFunction<RegisterMutation, RegisterMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useRegisterMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useRegisterMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useRegisterMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [registerMutation, { data, loading, error }] = useRegisterMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useRegisterMutation(baseOptions?: Apollo.MutationHookOptions<RegisterMutation, RegisterMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<RegisterMutation, RegisterMutationVariables>(RegisterDocument, options);
|
||||
}
|
||||
export type RegisterMutationHookResult = ReturnType<typeof useRegisterMutation>;
|
||||
export type RegisterMutationResult = Apollo.MutationResult<RegisterMutation>;
|
||||
export type RegisterMutationOptions = Apollo.BaseMutationOptions<RegisterMutation, RegisterMutationVariables>;
|
||||
export const StartAppDocument = gql`
|
||||
mutation StartApp($id: String!) {
|
||||
startApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type StartAppMutationFn = Apollo.MutationFunction<StartAppMutation, StartAppMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useStartAppMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useStartAppMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useStartAppMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [startAppMutation, { data, loading, error }] = useStartAppMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useStartAppMutation(baseOptions?: Apollo.MutationHookOptions<StartAppMutation, StartAppMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<StartAppMutation, StartAppMutationVariables>(StartAppDocument, options);
|
||||
}
|
||||
export type StartAppMutationHookResult = ReturnType<typeof useStartAppMutation>;
|
||||
export type StartAppMutationResult = Apollo.MutationResult<StartAppMutation>;
|
||||
export type StartAppMutationOptions = Apollo.BaseMutationOptions<StartAppMutation, StartAppMutationVariables>;
|
||||
export const StopAppDocument = gql`
|
||||
mutation StopApp($id: String!) {
|
||||
stopApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type StopAppMutationFn = Apollo.MutationFunction<StopAppMutation, StopAppMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useStopAppMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useStopAppMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useStopAppMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [stopAppMutation, { data, loading, error }] = useStopAppMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useStopAppMutation(baseOptions?: Apollo.MutationHookOptions<StopAppMutation, StopAppMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<StopAppMutation, StopAppMutationVariables>(StopAppDocument, options);
|
||||
}
|
||||
export type StopAppMutationHookResult = ReturnType<typeof useStopAppMutation>;
|
||||
export type StopAppMutationResult = Apollo.MutationResult<StopAppMutation>;
|
||||
export type StopAppMutationOptions = Apollo.BaseMutationOptions<StopAppMutation, StopAppMutationVariables>;
|
||||
export const UninstallAppDocument = gql`
|
||||
mutation UninstallApp($id: String!) {
|
||||
uninstallApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UninstallAppMutationFn = Apollo.MutationFunction<UninstallAppMutation, UninstallAppMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUninstallAppMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUninstallAppMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUninstallAppMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [uninstallAppMutation, { data, loading, error }] = useUninstallAppMutation({
|
||||
* variables: {
|
||||
* id: // value for 'id'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUninstallAppMutation(baseOptions?: Apollo.MutationHookOptions<UninstallAppMutation, UninstallAppMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<UninstallAppMutation, UninstallAppMutationVariables>(UninstallAppDocument, options);
|
||||
}
|
||||
export type UninstallAppMutationHookResult = ReturnType<typeof useUninstallAppMutation>;
|
||||
export type UninstallAppMutationResult = Apollo.MutationResult<UninstallAppMutation>;
|
||||
export type UninstallAppMutationOptions = Apollo.BaseMutationOptions<UninstallAppMutation, UninstallAppMutationVariables>;
|
||||
export const UpdateAppConfigDocument = gql`
|
||||
mutation UpdateAppConfig($input: AppInputType!) {
|
||||
updateAppConfig(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
export type UpdateAppConfigMutationFn = Apollo.MutationFunction<UpdateAppConfigMutation, UpdateAppConfigMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useUpdateAppConfigMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useUpdateAppConfigMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useUpdateAppConfigMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [updateAppConfigMutation, { data, loading, error }] = useUpdateAppConfigMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useUpdateAppConfigMutation(baseOptions?: Apollo.MutationHookOptions<UpdateAppConfigMutation, UpdateAppConfigMutationVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useMutation<UpdateAppConfigMutation, UpdateAppConfigMutationVariables>(UpdateAppConfigDocument, options);
|
||||
}
|
||||
export type UpdateAppConfigMutationHookResult = ReturnType<typeof useUpdateAppConfigMutation>;
|
||||
export type UpdateAppConfigMutationResult = Apollo.MutationResult<UpdateAppConfigMutation>;
|
||||
export type UpdateAppConfigMutationOptions = Apollo.BaseMutationOptions<UpdateAppConfigMutation, UpdateAppConfigMutationVariables>;
|
||||
export const GetAppDocument = gql`
|
||||
query GetApp($appId: String!) {
|
||||
getApp(id: $appId) {
|
||||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
port
|
||||
name
|
||||
description
|
||||
available
|
||||
version
|
||||
image
|
||||
short_desc
|
||||
author
|
||||
source
|
||||
categories
|
||||
url_suffix
|
||||
form_fields {
|
||||
type
|
||||
label
|
||||
max
|
||||
min
|
||||
hint
|
||||
required
|
||||
env_variable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetAppQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useGetAppQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useGetAppQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useGetAppQuery({
|
||||
* variables: {
|
||||
* appId: // value for 'appId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useGetAppQuery(baseOptions: Apollo.QueryHookOptions<GetAppQuery, GetAppQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<GetAppQuery, GetAppQueryVariables>(GetAppDocument, options);
|
||||
}
|
||||
export function useGetAppLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetAppQuery, GetAppQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<GetAppQuery, GetAppQueryVariables>(GetAppDocument, options);
|
||||
}
|
||||
export type GetAppQueryHookResult = ReturnType<typeof useGetAppQuery>;
|
||||
export type GetAppLazyQueryHookResult = ReturnType<typeof useGetAppLazyQuery>;
|
||||
export type GetAppQueryResult = Apollo.QueryResult<GetAppQuery, GetAppQueryVariables>;
|
||||
export const InstalledAppsDocument = gql`
|
||||
query InstalledApps {
|
||||
installedApps {
|
||||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
name
|
||||
description
|
||||
image
|
||||
short_desc
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useInstalledAppsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useInstalledAppsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useInstalledAppsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useInstalledAppsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useInstalledAppsQuery(baseOptions?: Apollo.QueryHookOptions<InstalledAppsQuery, InstalledAppsQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<InstalledAppsQuery, InstalledAppsQueryVariables>(InstalledAppsDocument, options);
|
||||
}
|
||||
export function useInstalledAppsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<InstalledAppsQuery, InstalledAppsQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<InstalledAppsQuery, InstalledAppsQueryVariables>(InstalledAppsDocument, options);
|
||||
}
|
||||
export type InstalledAppsQueryHookResult = ReturnType<typeof useInstalledAppsQuery>;
|
||||
export type InstalledAppsLazyQueryHookResult = ReturnType<typeof useInstalledAppsLazyQuery>;
|
||||
export type InstalledAppsQueryResult = Apollo.QueryResult<InstalledAppsQuery, InstalledAppsQueryVariables>;
|
||||
export const ConfiguredDocument = gql`
|
||||
query Configured {
|
||||
isConfigured
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useConfiguredQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useConfiguredQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useConfiguredQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useConfiguredQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useConfiguredQuery(baseOptions?: Apollo.QueryHookOptions<ConfiguredQuery, ConfiguredQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<ConfiguredQuery, ConfiguredQueryVariables>(ConfiguredDocument, options);
|
||||
}
|
||||
export function useConfiguredLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ConfiguredQuery, ConfiguredQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<ConfiguredQuery, ConfiguredQueryVariables>(ConfiguredDocument, options);
|
||||
}
|
||||
export type ConfiguredQueryHookResult = ReturnType<typeof useConfiguredQuery>;
|
||||
export type ConfiguredLazyQueryHookResult = ReturnType<typeof useConfiguredLazyQuery>;
|
||||
export type ConfiguredQueryResult = Apollo.QueryResult<ConfiguredQuery, ConfiguredQueryVariables>;
|
||||
export const ListAppsDocument = gql`
|
||||
query ListApps {
|
||||
listAppsInfo {
|
||||
apps {
|
||||
id
|
||||
available
|
||||
image
|
||||
port
|
||||
name
|
||||
version
|
||||
short_desc
|
||||
author
|
||||
categories
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useListAppsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useListAppsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useListAppsQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useListAppsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useListAppsQuery(baseOptions?: Apollo.QueryHookOptions<ListAppsQuery, ListAppsQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<ListAppsQuery, ListAppsQueryVariables>(ListAppsDocument, options);
|
||||
}
|
||||
export function useListAppsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ListAppsQuery, ListAppsQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<ListAppsQuery, ListAppsQueryVariables>(ListAppsDocument, options);
|
||||
}
|
||||
export type ListAppsQueryHookResult = ReturnType<typeof useListAppsQuery>;
|
||||
export type ListAppsLazyQueryHookResult = ReturnType<typeof useListAppsLazyQuery>;
|
||||
export type ListAppsQueryResult = Apollo.QueryResult<ListAppsQuery, ListAppsQueryVariables>;
|
||||
export const MeDocument = gql`
|
||||
query Me {
|
||||
me {
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useMeQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useMeQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useMeQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useMeQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useMeQuery(baseOptions?: Apollo.QueryHookOptions<MeQuery, MeQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<MeQuery, MeQueryVariables>(MeDocument, options);
|
||||
}
|
||||
export function useMeLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<MeQuery, MeQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<MeQuery, MeQueryVariables>(MeDocument, options);
|
||||
}
|
||||
export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
||||
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
||||
export type MeQueryResult = Apollo.QueryResult<MeQuery, MeQueryVariables>;
|
||||
export const SystemInfoDocument = gql`
|
||||
query SystemInfo {
|
||||
systemInfo {
|
||||
cpu {
|
||||
load
|
||||
}
|
||||
disk {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
memory {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useSystemInfoQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useSystemInfoQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useSystemInfoQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useSystemInfoQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useSystemInfoQuery(baseOptions?: Apollo.QueryHookOptions<SystemInfoQuery, SystemInfoQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<SystemInfoQuery, SystemInfoQueryVariables>(SystemInfoDocument, options);
|
||||
}
|
||||
export function useSystemInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SystemInfoQuery, SystemInfoQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<SystemInfoQuery, SystemInfoQueryVariables>(SystemInfoDocument, options);
|
||||
}
|
||||
export type SystemInfoQueryHookResult = ReturnType<typeof useSystemInfoQuery>;
|
||||
export type SystemInfoLazyQueryHookResult = ReturnType<typeof useSystemInfoLazyQuery>;
|
||||
export type SystemInfoQueryResult = Apollo.QueryResult<SystemInfoQuery, SystemInfoQueryVariables>;
|
||||
export const VersionDocument = gql`
|
||||
query Version {
|
||||
version {
|
||||
current
|
||||
latest
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useVersionQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useVersionQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useVersionQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useVersionQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useVersionQuery(baseOptions?: Apollo.QueryHookOptions<VersionQuery, VersionQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useQuery<VersionQuery, VersionQueryVariables>(VersionDocument, options);
|
||||
}
|
||||
export function useVersionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<VersionQuery, VersionQueryVariables>) {
|
||||
const options = { ...defaultOptions, ...baseOptions };
|
||||
return Apollo.useLazyQuery<VersionQuery, VersionQueryVariables>(VersionDocument, options);
|
||||
}
|
||||
export type VersionQueryHookResult = ReturnType<typeof useVersionQuery>;
|
||||
export type VersionLazyQueryHookResult = ReturnType<typeof useVersionLazyQuery>;
|
||||
export type VersionQueryResult = Apollo.QueryResult<VersionQuery, VersionQueryVariables>;
|
|
@ -0,0 +1,7 @@
|
|||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
7
packages/dashboard/src/graphql/mutations/login.graphql
Normal file
7
packages/dashboard/src/graphql/mutations/login.graphql
Normal file
|
@ -0,0 +1,7 @@
|
|||
mutation Login($input: UsernamePasswordInput!) {
|
||||
login(input: $input) {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
3
packages/dashboard/src/graphql/mutations/logout.graphql
Normal file
3
packages/dashboard/src/graphql/mutations/logout.graphql
Normal file
|
@ -0,0 +1,3 @@
|
|||
mutation Logout {
|
||||
logout
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mutation Register($input: UsernamePasswordInput!) {
|
||||
register(input: $input) {
|
||||
user {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mutation StartApp($id: String!) {
|
||||
startApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
7
packages/dashboard/src/graphql/mutations/stopApp.graphql
Normal file
7
packages/dashboard/src/graphql/mutations/stopApp.graphql
Normal file
|
@ -0,0 +1,7 @@
|
|||
mutation StopApp($id: String!) {
|
||||
stopApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mutation UninstallApp($id: String!) {
|
||||
uninstallApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mutation UpdateAppConfig($input: AppInputType!) {
|
||||
updateAppConfig(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
30
packages/dashboard/src/graphql/queries/getApp.graphql
Normal file
30
packages/dashboard/src/graphql/queries/getApp.graphql
Normal file
|
@ -0,0 +1,30 @@
|
|||
query GetApp($appId: String!) {
|
||||
getApp(id: $appId) {
|
||||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
port
|
||||
name
|
||||
description
|
||||
available
|
||||
version
|
||||
image
|
||||
short_desc
|
||||
author
|
||||
source
|
||||
categories
|
||||
url_suffix
|
||||
form_fields {
|
||||
type
|
||||
label
|
||||
max
|
||||
min
|
||||
hint
|
||||
required
|
||||
env_variable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
packages/dashboard/src/graphql/queries/installedApps.graphql
Normal file
14
packages/dashboard/src/graphql/queries/installedApps.graphql
Normal file
|
@ -0,0 +1,14 @@
|
|||
query InstalledApps {
|
||||
installedApps {
|
||||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
name
|
||||
description
|
||||
image
|
||||
short_desc
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
query Configured {
|
||||
isConfigured
|
||||
}
|
17
packages/dashboard/src/graphql/queries/listApps.graphql
Normal file
17
packages/dashboard/src/graphql/queries/listApps.graphql
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Write your query or mutation here
|
||||
query ListApps {
|
||||
listAppsInfo {
|
||||
apps {
|
||||
id
|
||||
available
|
||||
image
|
||||
port
|
||||
name
|
||||
version
|
||||
short_desc
|
||||
author
|
||||
categories
|
||||
}
|
||||
total
|
||||
}
|
||||
}
|
5
packages/dashboard/src/graphql/queries/me.graphql
Normal file
5
packages/dashboard/src/graphql/queries/me.graphql
Normal file
|
@ -0,0 +1,5 @@
|
|||
query Me {
|
||||
me {
|
||||
id
|
||||
}
|
||||
}
|
17
packages/dashboard/src/graphql/queries/systemInfo.graphql
Normal file
17
packages/dashboard/src/graphql/queries/systemInfo.graphql
Normal file
|
@ -0,0 +1,17 @@
|
|||
query SystemInfo {
|
||||
systemInfo {
|
||||
cpu {
|
||||
load
|
||||
}
|
||||
disk {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
memory {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
}
|
||||
}
|
6
packages/dashboard/src/graphql/queries/version.graphql
Normal file
6
packages/dashboard/src/graphql/queries/version.graphql
Normal file
|
@ -0,0 +1,6 @@
|
|||
query Version {
|
||||
version {
|
||||
current
|
||||
latest
|
||||
}
|
||||
}
|
49
packages/dashboard/src/hooks/useCachedRessources.ts
Normal file
49
packages/dashboard/src/hooks/useCachedRessources.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { ApolloClient } from '@apollo/client';
|
||||
import axios from 'axios';
|
||||
import useSWR, { BareFetcher } from 'swr';
|
||||
import { createApolloClient } from '../core/apollo/client';
|
||||
import { useSytemStore } from '../state/systemStore';
|
||||
|
||||
interface IReturnProps {
|
||||
client?: ApolloClient<unknown>;
|
||||
isLoadingComplete?: boolean;
|
||||
}
|
||||
|
||||
const fetcher: BareFetcher<any> = (url: string) => {
|
||||
return axios.get(url).then((res) => res.data);
|
||||
};
|
||||
|
||||
export default function useCachedResources(): IReturnProps {
|
||||
const { data } = useSWR('/api/ip', fetcher);
|
||||
const { internalIp, setInternalIp } = useSytemStore();
|
||||
const [isLoadingComplete, setLoadingComplete] = useState(false);
|
||||
const [client, setClient] = useState<ApolloClient<unknown>>();
|
||||
|
||||
async function loadResourcesAndDataAsync(ip: string) {
|
||||
try {
|
||||
const restoredClient = await createApolloClient(ip);
|
||||
|
||||
setClient(restoredClient);
|
||||
} catch (error) {
|
||||
// We might want to provide this error information to an error reporting service
|
||||
console.warn(error);
|
||||
} finally {
|
||||
setLoadingComplete(true);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.ip && !internalIp) {
|
||||
setInternalIp(data.ip);
|
||||
}
|
||||
}, [data?.ip, internalIp, setInternalIp]);
|
||||
|
||||
useEffect(() => {
|
||||
if (internalIp) {
|
||||
loadResourcesAndDataAsync(internalIp);
|
||||
}
|
||||
}, [internalIp]);
|
||||
|
||||
return { client, isLoadingComplete };
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
import { Flex, Input, SimpleGrid } from '@chakra-ui/react';
|
||||
import { AppCategoriesEnum, AppConfig } from '@runtipi/common';
|
||||
import React from 'react';
|
||||
import { SortableColumns, SortDirection } from '../helpers/table.types';
|
||||
import { AppCategoriesEnum } from '../../../generated/graphql';
|
||||
import { AppTableData, SortableColumns, SortDirection } from '../helpers/table.types';
|
||||
import AppStoreTile from './AppStoreTile';
|
||||
import CategorySelect from './CategorySelect';
|
||||
|
||||
interface IProps {
|
||||
data: AppConfig[];
|
||||
data: AppTableData;
|
||||
onSearch: (value: string) => void;
|
||||
onSelectCategories: (value: AppCategoriesEnum[]) => void;
|
||||
onSortBy: (value: SortableColumns) => void;
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import { Tag, TagLabel } from '@chakra-ui/react';
|
||||
import { AppConfig } from '@runtipi/common';
|
||||
import Link from 'next/link';
|
||||
import React from 'react';
|
||||
import AppLogo from '../../../components/AppLogo/AppLogo';
|
||||
import { AppCategoriesEnum } from '../../../generated/graphql';
|
||||
import { colorSchemeForCategory, limitText } from '../helpers/table.helpers';
|
||||
|
||||
const AppStoreTile: React.FC<{ app: AppConfig }> = ({ app }) => {
|
||||
type App = {
|
||||
id: string;
|
||||
name: string;
|
||||
categories: string[];
|
||||
short_desc: string;
|
||||
image: string;
|
||||
};
|
||||
|
||||
const AppStoreTile: React.FC<{ app: App }> = ({ app }) => {
|
||||
return (
|
||||
<Link href={`/app-store/${app.id}`} passHref>
|
||||
<div key={app.id} className="p-2 rounded-md app-store-tile flex items-center group">
|
||||
|
@ -14,8 +22,8 @@ const AppStoreTile: React.FC<{ app: AppConfig }> = ({ app }) => {
|
|||
<div className="font-bold">{limitText(app.name, 20)}</div>
|
||||
<div className="text-sm mb-1">{limitText(app.short_desc, 45)}</div>
|
||||
{app.categories?.map((category) => (
|
||||
<Tag colorScheme={colorSchemeForCategory[category]} className="mr-1" borderRadius="full" key={`${app.id}-${category}`} size="sm" variant="solid">
|
||||
<TagLabel>{category}</TagLabel>
|
||||
<Tag colorScheme={colorSchemeForCategory[category as AppCategoriesEnum]} className="mr-1" borderRadius="full" key={`${app.id}-${category}`} size="sm" variant="solid">
|
||||
<TagLabel>{category.toLocaleLowerCase()}</TagLabel>
|
||||
</Tag>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { useColorModeValue } from '@chakra-ui/react';
|
||||
import { AppCategoriesEnum, APP_CATEGORIES } from '@runtipi/common';
|
||||
import React from 'react';
|
||||
import Select, { Options } from 'react-select';
|
||||
import { APP_CATEGORIES } from '../../../core/constants';
|
||||
import { AppCategoriesEnum } from '../../../generated/graphql';
|
||||
|
||||
interface IProps {
|
||||
onSelect: (value: AppCategoriesEnum[]) => void;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { AppConfig } from '@runtipi/common';
|
||||
import React from 'react';
|
||||
import { Box, Button, Flex } from '@chakra-ui/react';
|
||||
import FeaturedCard from './FeaturedCard';
|
||||
import { AppInfo } from '../../../generated/graphql';
|
||||
|
||||
interface IProps {
|
||||
apps: AppConfig[];
|
||||
apps: AppInfo[];
|
||||
}
|
||||
|
||||
const FeaturedApps: React.FC<IProps> = ({ apps }) => {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Flex, ScaleFade } from '@chakra-ui/react';
|
||||
import { AppConfig } from '@runtipi/common';
|
||||
import React from 'react';
|
||||
import { AppInfo } from '../../../generated/graphql';
|
||||
|
||||
interface IProps {
|
||||
app: AppConfig;
|
||||
app: AppInfo;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ const FeaturedCard: React.FC<IProps> = ({ app, show }) => {
|
|||
rounded="md"
|
||||
shadow="md"
|
||||
style={{
|
||||
backgroundImage: `url(https://images.unsplash.com/photo-1488590528505-98d2b5aba04b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80)`,
|
||||
backgroundImage: 'url(https://images.unsplash.com/photo-1488590528505-98d2b5aba04b?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80)',
|
||||
}}
|
||||
>
|
||||
<div className="relative flex flex-1 w-max lg:bg-gradient-to-r from-white via-white">
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import { Flex } from '@chakra-ui/react';
|
||||
import { AppCategoriesEnum } from '@runtipi/common';
|
||||
import React from 'react';
|
||||
import { useAppsStore } from '../../../state/appsStore';
|
||||
import { AppCategoriesEnum } from '../../../generated/graphql';
|
||||
import AppStoreTable from '../components/AppStoreTable';
|
||||
import { sortTable } from '../helpers/table.helpers';
|
||||
import { SortableColumns, SortDirection } from '../helpers/table.types';
|
||||
import { AppTableData, SortableColumns, SortDirection } from '../helpers/table.types';
|
||||
|
||||
// function nonNullable<T>(value: T): value is NonNullable<T> {
|
||||
// return value !== null && value !== undefined;
|
||||
// }
|
||||
|
||||
const AppStoreContainer = () => {
|
||||
const { apps } = useAppsStore();
|
||||
interface IProps {
|
||||
apps: AppTableData;
|
||||
}
|
||||
|
||||
const AppStoreContainer: React.FC<IProps> = ({ apps }) => {
|
||||
const [search, setSearch] = React.useState('');
|
||||
const [categories, setCategories] = React.useState<AppCategoriesEnum[]>([]);
|
||||
const [sort, setSort] = React.useState<SortableColumns>('name');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { AppCategoriesEnum, AppConfig } from '@runtipi/common';
|
||||
import { AppCategoriesEnum, AppInfo } from '../../../generated/graphql';
|
||||
import { AppTableData } from './table.types';
|
||||
|
||||
export const sortTable = (data: AppConfig[], col: keyof Pick<AppConfig, 'name'>, direction: 'asc' | 'desc', categories: AppCategoriesEnum[], search: string) => {
|
||||
export const sortTable = (data: AppTableData, col: keyof Pick<AppInfo, 'name'>, direction: 'asc' | 'desc', categories: AppCategoriesEnum[], search: string) => {
|
||||
const sortedData = [...data].sort((a, b) => {
|
||||
const aVal = a[col];
|
||||
const bVal = b[col];
|
||||
|
@ -25,15 +26,15 @@ export const limitText = (text: string, limit: number) => {
|
|||
};
|
||||
|
||||
export const colorSchemeForCategory: Record<AppCategoriesEnum, string> = {
|
||||
[AppCategoriesEnum.NETWORK]: 'blue',
|
||||
[AppCategoriesEnum.MEDIA]: 'green',
|
||||
[AppCategoriesEnum.AUTOMATION]: 'orange',
|
||||
[AppCategoriesEnum.DEVELOPMENT]: 'purple',
|
||||
[AppCategoriesEnum.UTILITIES]: 'gray',
|
||||
[AppCategoriesEnum.PHOTOGRAPHY]: 'red',
|
||||
[AppCategoriesEnum.SECURITY]: 'yellow',
|
||||
[AppCategoriesEnum.SOCIAL]: 'teal',
|
||||
[AppCategoriesEnum.FEATURED]: 'pink',
|
||||
[AppCategoriesEnum.DATA]: 'red',
|
||||
[AppCategoriesEnum.BOOKS]: 'blue',
|
||||
[AppCategoriesEnum.Network]: 'blue',
|
||||
[AppCategoriesEnum.Media]: 'green',
|
||||
[AppCategoriesEnum.Automation]: 'orange',
|
||||
[AppCategoriesEnum.Development]: 'purple',
|
||||
[AppCategoriesEnum.Utilities]: 'gray',
|
||||
[AppCategoriesEnum.Photography]: 'red',
|
||||
[AppCategoriesEnum.Security]: 'yellow',
|
||||
[AppCategoriesEnum.Social]: 'teal',
|
||||
[AppCategoriesEnum.Featured]: 'pink',
|
||||
[AppCategoriesEnum.Data]: 'red',
|
||||
[AppCategoriesEnum.Books]: 'blue',
|
||||
};
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { AppConfig } from '@runtipi/common';
|
||||
import { AppInfo } from '../../../generated/graphql';
|
||||
|
||||
export type SortableColumns = keyof Pick<AppConfig, 'name'>;
|
||||
export type SortableColumns = keyof Pick<AppInfo, 'name'>;
|
||||
export type SortDirection = 'asc' | 'desc';
|
||||
|
||||
export type AppTableData = Omit<AppInfo, 'description' | 'form_fields' | 'source' | 'status' | 'url_suffix' | 'version'>[];
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
import { Button } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { FiExternalLink, FiPause, FiPlay, FiSettings, FiTrash2 } from 'react-icons/fi';
|
||||
import { AppConfig, AppStatusEnum } from '@runtipi/common';
|
||||
import { TiCancel } from 'react-icons/ti';
|
||||
import { AppInfo, AppStatusEnum } from '../../../generated/graphql';
|
||||
|
||||
interface IProps {
|
||||
app: AppConfig;
|
||||
app: AppInfo;
|
||||
status?: AppStatusEnum;
|
||||
onInstall: () => void;
|
||||
onUninstall: () => void;
|
||||
onStart: () => void;
|
||||
onStop: () => void;
|
||||
onOpen: () => void;
|
||||
onUpdate: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const AppActions: React.FC<IProps> = ({ app, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate }) => {
|
||||
const AppActions: React.FC<IProps> = ({ app, status, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate, onCancel }) => {
|
||||
const hasSettings = Object.keys(app.form_fields).length > 0;
|
||||
|
||||
if (app?.installed && app.status === AppStatusEnum.STOPPED) {
|
||||
if (status === AppStatusEnum.Stopped) {
|
||||
return (
|
||||
<div className="flex flex-wrap justify-center">
|
||||
<Button onClick={onStart} width={150} colorScheme="green" className="mt-3 mr-2">
|
||||
|
@ -35,7 +38,7 @@ const AppActions: React.FC<IProps> = ({ app, onInstall, onUninstall, onStart, on
|
|||
)}
|
||||
</div>
|
||||
);
|
||||
} else if (app?.installed && app.status === AppStatusEnum.RUNNING) {
|
||||
} else if (status === AppStatusEnum.Running) {
|
||||
return (
|
||||
<div>
|
||||
<Button onClick={onOpen} width={150} colorScheme="gray" className="mt-3 mr-2">
|
||||
|
@ -48,23 +51,28 @@ const AppActions: React.FC<IProps> = ({ app, onInstall, onUninstall, onStart, on
|
|||
</Button>
|
||||
</div>
|
||||
);
|
||||
} else if (app.status === AppStatusEnum.INSTALLING || app.status === AppStatusEnum.UNINSTALLING || app.status === AppStatusEnum.STARTING || app.status === AppStatusEnum.STOPPING) {
|
||||
} else if (status === AppStatusEnum.Installing || status === AppStatusEnum.Uninstalling || status === AppStatusEnum.Starting || status === AppStatusEnum.Stopping) {
|
||||
return (
|
||||
<div className="flex items-center sm:items-start 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 self-center text-center sm:text-left">{`App is ${app.status} please wait and don't refresh page...`}</span>
|
||||
<Button onClick={onCancel} colorScheme="gray" className="mt-3 mr-2 ml-2">
|
||||
<TiCancel />
|
||||
</Button>
|
||||
<span className="text-gray-500 text-sm ml-2 mt-3 self-center text-center sm:text-left">{`App is ${status.toLowerCase()} please wait and don't refresh page...`}</span>
|
||||
</div>
|
||||
);
|
||||
} else if (status === AppStatusEnum.Missing) {
|
||||
return (
|
||||
<Button onClick={onInstall} width={160} colorScheme="green" className="mt-3">
|
||||
Install
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={onInstall} width={160} colorScheme="green" className="mt-3">
|
||||
Install
|
||||
</Button>
|
||||
);
|
||||
return null;
|
||||
};
|
||||
|
||||
export default AppActions;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue