App actions GraphQL
This commit is contained in:
parent
1132bf7ece
commit
62a12f1ab5
57 changed files with 15609 additions and 416 deletions
|
@ -4,3 +4,6 @@
|
|||
/.next
|
||||
node_modules
|
||||
.next
|
||||
dist/
|
||||
**/dist/
|
||||
**/next/
|
||||
|
|
2
.github/workflows/release-candidate.yml
vendored
2
.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}"
|
||||
|
||||
|
|
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}"
|
||||
|
||||
|
|
60
Dockerfile
60
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:latest as app
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install docker
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
RUN apk --no-cache --virtual build-dependencies add docker docker-compose curl nodejs npm bash
|
||||
|
||||
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:latest as app
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install docker
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
lsb-release
|
||||
RUN apk --no-cache --virtual build-dependencies add docker docker-compose curl 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
|
||||
|
|
|
@ -2,4 +2,5 @@ version: "3.7"
|
|||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
name: runtipi_tipi_main_network
|
||||
external:
|
||||
name: runtipi_tipi_main_network
|
|
@ -21,7 +21,7 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
command: bash -c "cd /api && npm run dev"
|
||||
command: /bin/sh -c "cd /api && npm run dev"
|
||||
depends_on:
|
||||
- tipi-db
|
||||
container_name: api
|
||||
|
@ -31,8 +31,8 @@ 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}
|
||||
|
@ -49,7 +49,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
|
||||
|
@ -58,9 +58,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`) &&
|
||||
|
@ -71,9 +71,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:
|
||||
|
|
|
@ -10,7 +10,8 @@
|
|||
"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",
|
||||
"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: 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"]
|
||||
}
|
|
@ -16,7 +16,6 @@
|
|||
"@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",
|
||||
|
|
|
@ -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>
|
||||
|
|
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' },
|
||||
];
|
|
@ -24,6 +24,7 @@ export type App = {
|
|||
config: Scalars['JSONObject'];
|
||||
createdAt: Scalars['DateTime'];
|
||||
id: Scalars['String'];
|
||||
info: AppInfo;
|
||||
lastOpened: Scalars['DateTime'];
|
||||
numOpened: Scalars['Float'];
|
||||
status: AppStatusEnum;
|
||||
|
@ -56,6 +57,7 @@ export type AppInfo = {
|
|||
installed: Scalars['Boolean'];
|
||||
name: Scalars['String'];
|
||||
port: Scalars['Float'];
|
||||
requirements?: Maybe<Requirements>;
|
||||
short_desc: Scalars['String'];
|
||||
source: Scalars['String'];
|
||||
url_suffix?: Maybe<Scalars['String']>;
|
||||
|
@ -67,14 +69,9 @@ export type AppInputType = {
|
|||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type AppResponse = {
|
||||
__typename?: 'AppResponse';
|
||||
app?: Maybe<App>;
|
||||
info: AppInfo;
|
||||
};
|
||||
|
||||
export enum AppStatusEnum {
|
||||
Installing = 'INSTALLING',
|
||||
Missing = 'MISSING',
|
||||
Running = 'RUNNING',
|
||||
Starting = 'STARTING',
|
||||
Stopped = 'STOPPED',
|
||||
|
@ -164,7 +161,7 @@ export type MutationUpdateAppConfigArgs = {
|
|||
|
||||
export type Query = {
|
||||
__typename?: 'Query';
|
||||
getApp: AppResponse;
|
||||
getApp: App;
|
||||
installedApps: Array<App>;
|
||||
isConfigured: Scalars['Boolean'];
|
||||
listAppsInfo: ListAppsResonse;
|
||||
|
@ -177,6 +174,11 @@ export type QueryGetAppArgs = {
|
|||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
export type Requirements = {
|
||||
__typename?: 'Requirements';
|
||||
ports?: Maybe<Array<Scalars['Float']>>;
|
||||
};
|
||||
|
||||
export type SystemInfoResponse = {
|
||||
__typename?: 'SystemInfoResponse';
|
||||
cpu: Cpu;
|
||||
|
@ -206,7 +208,7 @@ export type InstallAppMutationVariables = Exact<{
|
|||
input: AppInputType;
|
||||
}>;
|
||||
|
||||
export type InstallAppMutation = { __typename?: 'Mutation'; installApp: { __typename?: 'App'; id: string } };
|
||||
export type InstallAppMutation = { __typename?: 'Mutation'; installApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type LoginMutationVariables = Exact<{
|
||||
input: UsernamePasswordInput;
|
||||
|
@ -224,6 +226,30 @@ export type RegisterMutationVariables = Exact<{
|
|||
|
||||
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'];
|
||||
}>;
|
||||
|
@ -231,8 +257,10 @@ export type GetAppQueryVariables = Exact<{
|
|||
export type GetAppQuery = {
|
||||
__typename?: 'Query';
|
||||
getApp: {
|
||||
__typename?: 'AppResponse';
|
||||
app?: { __typename?: 'App'; id: string; status: AppStatusEnum; config: any } | null;
|
||||
__typename?: 'App';
|
||||
id: string;
|
||||
status: AppStatusEnum;
|
||||
config: any;
|
||||
info: {
|
||||
__typename?: 'AppInfo';
|
||||
id: string;
|
||||
|
@ -264,7 +292,16 @@ export type GetAppQuery = {
|
|||
|
||||
export type InstalledAppsQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type InstalledAppsQuery = { __typename?: 'Query'; installedApps: Array<{ __typename?: 'App'; id: string; status: AppStatusEnum; config: any }> };
|
||||
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 }>;
|
||||
|
||||
|
@ -310,9 +347,11 @@ export type SystemInfoQuery = {
|
|||
};
|
||||
|
||||
export const InstallAppDocument = gql`
|
||||
mutation installApp($input: AppInputType!) {
|
||||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
@ -442,14 +481,152 @@ export function useRegisterMutation(baseOptions?: Apollo.MutationHookOptions<Reg
|
|||
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) {
|
||||
app {
|
||||
id
|
||||
status
|
||||
config
|
||||
}
|
||||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
port
|
||||
|
@ -511,6 +688,13 @@ export const InstalledAppsDocument = gql`
|
|||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
name
|
||||
description
|
||||
image
|
||||
short_desc
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mutation installApp($input: AppInputType!) {
|
||||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
query GetApp($appId: String!) {
|
||||
getApp(id: $appId) {
|
||||
app {
|
||||
id
|
||||
status
|
||||
config
|
||||
}
|
||||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
port
|
||||
|
|
|
@ -3,5 +3,12 @@ query InstalledApps {
|
|||
id
|
||||
status
|
||||
config
|
||||
info {
|
||||
id
|
||||
name
|
||||
description
|
||||
image
|
||||
short_desc
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Flex, Input, SimpleGrid } from '@chakra-ui/react';
|
||||
import { AppCategoriesEnum } from '@runtipi/common';
|
||||
import React from 'react';
|
||||
import { AppCategoriesEnum } from '../../../generated/graphql';
|
||||
import { AppTableData, SortableColumns, SortDirection } from '../helpers/table.types';
|
||||
import AppStoreTile from './AppStoreTile';
|
||||
import CategorySelect from './CategorySelect';
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Tag, TagLabel } from '@chakra-ui/react';
|
||||
import { AppCategoriesEnum } 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';
|
||||
|
||||
type App = {
|
||||
|
@ -23,7 +23,7 @@ const AppStoreTile: React.FC<{ app: App }> = ({ app }) => {
|
|||
<div className="text-sm mb-1">{limitText(app.short_desc, 45)}</div>
|
||||
{app.categories?.map((category) => (
|
||||
<Tag colorScheme={colorSchemeForCategory[category as AppCategoriesEnum]} className="mr-1" borderRadius="full" key={`${app.id}-${category}`} size="sm" variant="solid">
|
||||
<TagLabel>{category}</TagLabel>
|
||||
<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,6 +1,6 @@
|
|||
import { Flex } from '@chakra-ui/react';
|
||||
import { AppCategoriesEnum } from '@runtipi/common';
|
||||
import React from 'react';
|
||||
import { AppCategoriesEnum } from '../../../generated/graphql';
|
||||
import AppStoreTable from '../components/AppStoreTable';
|
||||
import { sortTable } from '../helpers/table.helpers';
|
||||
import { AppTableData, SortableColumns, SortDirection } from '../helpers/table.types';
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { AppCategoriesEnum } from '@runtipi/common';
|
||||
import { AppConfig } from '../../../generated/graphql';
|
||||
import { AppCategoriesEnum, AppInfo } from '../../../generated/graphql';
|
||||
import { AppTableData } from './table.types';
|
||||
|
||||
export const sortTable = (data: AppTableData, 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];
|
||||
|
@ -27,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,6 +1,6 @@
|
|||
import { AppConfig } from '../../../generated/graphql';
|
||||
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<AppConfig, 'description' | 'form_fields' | 'source' | 'status' | 'url_suffix' | 'version'>[];
|
||||
export type AppTableData = Omit<AppInfo, 'description' | 'form_fields' | 'source' | 'status' | 'url_suffix' | 'version'>[];
|
||||
|
|
|
@ -56,16 +56,18 @@ const AppActions: React.FC<IProps> = ({ app, status, onInstall, onUninstall, onS
|
|||
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 ${status} please wait and don't refresh page...`}</span>
|
||||
<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;
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { Form, Field } from 'react-final-form';
|
||||
import FormInput from '../../../components/Form/FormInput';
|
||||
import { validateAppConfig } from '../../../components/Form/validators';
|
||||
import { AppInfo } from '../../../generated/graphql';
|
||||
import { AppInfo, FormField } from '../../../generated/graphql';
|
||||
|
||||
interface IProps {
|
||||
formFields: AppInfo['form_fields'];
|
||||
|
@ -12,7 +12,7 @@ interface IProps {
|
|||
}
|
||||
|
||||
const InstallForm: React.FC<IProps> = ({ formFields, onSubmit, initalValues }) => {
|
||||
const renderField = (field: typeof formFields[0]) => {
|
||||
const renderField = (field: FormField) => {
|
||||
return (
|
||||
<Field
|
||||
key={field.env_variable}
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
import { Modal, ModalBody, ModalCloseButton, ModalContent, ModalHeader, ModalOverlay } from '@chakra-ui/react';
|
||||
import React, { useEffect } from 'react';
|
||||
import useSWR from 'swr';
|
||||
import fetcher from '../../../core/fetcher';
|
||||
import React from 'react';
|
||||
import InstallForm from './InstallForm';
|
||||
import { AppInfo } from '../../../generated/graphql';
|
||||
import { App, AppInfo } from '../../../generated/graphql';
|
||||
|
||||
interface IProps {
|
||||
app: AppInfo;
|
||||
config: App['config'];
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSubmit: (values: Record<string, any>) => void;
|
||||
}
|
||||
|
||||
const UpdateModal: React.FC<IProps> = ({ app, isOpen, onClose, onSubmit }) => {
|
||||
const { data, mutate } = useSWR<Record<string, string>>(`/apps/form/${app.id}`, fetcher, { refreshInterval: 10 });
|
||||
|
||||
useEffect(() => {
|
||||
mutate({}, true);
|
||||
}, [isOpen, mutate]);
|
||||
|
||||
const UpdateModal: React.FC<IProps> = ({ app, config, isOpen, onClose, onSubmit }) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
|
@ -26,7 +19,7 @@ const UpdateModal: React.FC<IProps> = ({ app, isOpen, onClose, onSubmit }) => {
|
|||
<ModalHeader>Update {app.name} config</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<InstallForm onSubmit={onSubmit} formFields={app.form_fields} initalValues={data} />
|
||||
<InstallForm onSubmit={onSubmit} formFields={app.form_fields} initalValues={config} />
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
|
|
@ -9,14 +9,24 @@ import UninstallModal from '../components/UninstallModal';
|
|||
import UpdateModal from '../components/UpdateModal';
|
||||
import AppLogo from '../../../components/AppLogo/AppLogo';
|
||||
import Markdown from '../../../components/Markdown/Markdown';
|
||||
import { AppInfo, AppStatusEnum, GetAppDocument, useInstallAppMutation } from '../../../generated/graphql';
|
||||
import {
|
||||
App,
|
||||
AppInfo,
|
||||
AppStatusEnum,
|
||||
GetAppDocument,
|
||||
useInstallAppMutation,
|
||||
useStartAppMutation,
|
||||
useStopAppMutation,
|
||||
useUninstallAppMutation,
|
||||
useUpdateAppConfigMutation,
|
||||
} from '../../../generated/graphql';
|
||||
|
||||
interface IProps {
|
||||
status?: AppStatusEnum;
|
||||
app?: Pick<App, 'status' | 'config'>;
|
||||
info: AppInfo;
|
||||
}
|
||||
|
||||
const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
||||
const AppDetails: React.FC<IProps> = ({ app, info }) => {
|
||||
const toast = useToast();
|
||||
const installDisclosure = useDisclosure();
|
||||
const uninstallDisclosure = useDisclosure();
|
||||
|
@ -25,6 +35,10 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
|
||||
// Mutations
|
||||
const [install] = useInstallAppMutation({ refetchQueries: [{ query: GetAppDocument, variables: { appId: info.id } }] });
|
||||
const [uninstall] = useUninstallAppMutation({ refetchQueries: [{ query: GetAppDocument, variables: { appId: info.id } }] });
|
||||
const [stop] = useStopAppMutation({ refetchQueries: [{ query: GetAppDocument, variables: { appId: info.id } }] });
|
||||
const [start] = useStartAppMutation({ refetchQueries: [{ query: GetAppDocument, variables: { appId: info.id } }] });
|
||||
const [update] = useUpdateAppConfigMutation({ refetchQueries: [{ query: GetAppDocument, variables: { appId: info.id } }] });
|
||||
|
||||
const { internalIp } = useSytemStore();
|
||||
|
||||
|
@ -43,7 +57,7 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
const handleInstallSubmit = async (values: Record<string, any>) => {
|
||||
installDisclosure.onClose();
|
||||
try {
|
||||
await install({ variables: { input: { form: values, id: info.id } } });
|
||||
await install({ variables: { input: { form: values, id: info.id } }, optimisticResponse: { installApp: { id: info.id, status: AppStatusEnum.Installing, __typename: 'App' } } });
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
|
@ -52,7 +66,7 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
const handleUnistallSubmit = async () => {
|
||||
uninstallDisclosure.onClose();
|
||||
try {
|
||||
await uninstall(info.id);
|
||||
await uninstall({ variables: { id: info.id }, optimisticResponse: { uninstallApp: { id: info.id, status: AppStatusEnum.Uninstalling, __typename: 'App' } } });
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
|
@ -61,7 +75,7 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
const handleStopSubmit = async () => {
|
||||
stopDisclosure.onClose();
|
||||
try {
|
||||
await stop(info.id);
|
||||
await stop({ variables: { id: info.id }, optimisticResponse: { stopApp: { id: info.id, status: AppStatusEnum.Stopping, __typename: 'App' } } });
|
||||
} catch (error) {
|
||||
handleError(error);
|
||||
}
|
||||
|
@ -69,7 +83,7 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
|
||||
const handleStartSubmit = async () => {
|
||||
try {
|
||||
await start(info.id);
|
||||
await start({ variables: { id: info.id }, optimisticResponse: { startApp: { id: info.id, status: AppStatusEnum.Starting, __typename: 'App' } } });
|
||||
} catch (e: unknown) {
|
||||
handleError(e);
|
||||
}
|
||||
|
@ -77,7 +91,7 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
|
||||
const handleUpdateSubmit = async (values: Record<string, any>) => {
|
||||
try {
|
||||
await update(info.id, values);
|
||||
await update({ variables: { input: { form: values, id: info.id } } });
|
||||
toast({
|
||||
title: 'Success',
|
||||
description: 'App config updated successfully',
|
||||
|
@ -122,7 +136,7 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
onUninstall={uninstallDisclosure.onOpen}
|
||||
onInstall={installDisclosure.onOpen}
|
||||
app={info}
|
||||
status={status}
|
||||
status={app?.status}
|
||||
/>
|
||||
</div>
|
||||
</VStack>
|
||||
|
@ -132,7 +146,7 @@ const AppDetails: React.FC<IProps> = ({ status, info }) => {
|
|||
<InstallModal onSubmit={handleInstallSubmit} isOpen={installDisclosure.isOpen} onClose={installDisclosure.onClose} app={info} />
|
||||
<UninstallModal onConfirm={handleUnistallSubmit} isOpen={uninstallDisclosure.isOpen} onClose={uninstallDisclosure.onClose} app={info} />
|
||||
<StopModal onConfirm={handleStopSubmit} isOpen={stopDisclosure.isOpen} onClose={stopDisclosure.onClose} app={info} />
|
||||
<UpdateModal onSubmit={handleUpdateSubmit} isOpen={updateDisclosure.isOpen} onClose={updateDisclosure.onClose} app={info} />
|
||||
<UpdateModal onSubmit={handleUpdateSubmit} isOpen={updateDisclosure.isOpen} onClose={updateDisclosure.onClose} app={info} config={app?.config} />
|
||||
</div>
|
||||
</SlideFade>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ const AppDetailsPage: NextPage<IProps> = ({ appId }) => {
|
|||
|
||||
return (
|
||||
<Layout breadcrumbs={breadcrumb} loading={!data?.getApp && loading}>
|
||||
{data?.getApp.info && <AppDetails status={data?.getApp.app?.status} info={data?.getApp.info} />}
|
||||
{data?.getApp.info && <AppDetails app={data?.getApp} info={data?.getApp.info} />}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,8 +16,8 @@ const AppDetailsPage: NextPage<IProps> = ({ appId }) => {
|
|||
];
|
||||
|
||||
return (
|
||||
<Layout breadcrumbs={breadcrumb} loading={!data?.getApp.app && loading}>
|
||||
{data?.getApp.info && <AppDetails status={data?.getApp.app?.status} info={data.getApp.info} />}
|
||||
<Layout breadcrumbs={breadcrumb} loading={!data?.getApp && loading}>
|
||||
{data?.getApp.info && <AppDetails app={data?.getApp} info={data.getApp.info} />}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,25 +1,17 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React from 'react';
|
||||
import { Flex, SimpleGrid } from '@chakra-ui/react';
|
||||
import type { NextPage } from 'next';
|
||||
import Layout from '../../components/Layout';
|
||||
import { RequestStatus } from '../../core/types';
|
||||
import { useAppsStore } from '../../state/appsStore';
|
||||
import AppTile from '../../components/AppTile';
|
||||
import { useInstalledAppsQuery } from '../../generated/graphql';
|
||||
|
||||
const Apps: NextPage = () => {
|
||||
const { fetch, status, apps } = useAppsStore((state) => state);
|
||||
const { data, loading } = useInstalledAppsQuery();
|
||||
|
||||
useEffect(() => {
|
||||
fetch();
|
||||
}, [fetch]);
|
||||
|
||||
const installed = apps.filter((app) => app.installed);
|
||||
|
||||
const installedCount: number = installed.length || 0;
|
||||
const loading = status === RequestStatus.LOADING && installedCount === 0;
|
||||
const installedCount: number = data?.installedApps.length || 0;
|
||||
|
||||
return (
|
||||
<Layout loading={loading}>
|
||||
<Layout loading={loading || !data?.installedApps}>
|
||||
<Flex className="flex-col">
|
||||
{installedCount > 0 && <h1 className="font-bold text-3xl mb-5">My Apps ({installedCount})</h1>}
|
||||
{installedCount === 0 && (
|
||||
|
@ -29,8 +21,8 @@ const Apps: NextPage = () => {
|
|||
</div>
|
||||
)}
|
||||
<SimpleGrid minChildWidth="340px" spacing="20px">
|
||||
{installed.map((app) => (
|
||||
<AppTile key={app.name} app={app} />
|
||||
{data?.installedApps.map((app) => (
|
||||
<AppTile key={app.id} app={app.info} status={app.status} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
</Flex>
|
||||
|
|
15124
packages/system-api/package-lock.json
generated
Normal file
15124
packages/system-api/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -12,7 +12,7 @@
|
|||
"lint": "eslint . --ext .ts",
|
||||
"test": "jest --colors",
|
||||
"test:watch": "jest --watch",
|
||||
"build": "esbuild --bundle src/server.ts --outdir=dist --allow-overwrite --sourcemap --platform=node --minify --analyze=verbose --external:./node_modules/* --format=esm",
|
||||
"build": "esbuild --bundle src/server.ts --outdir=dist --allow-overwrite --sourcemap --platform=node --analyze=verbose --external:./node_modules/* --format=esm",
|
||||
"build:watch": "esbuild --bundle src/server.ts --outdir=dist --allow-overwrite --sourcemap --platform=node --external:./node_modules/* --format=esm --watch",
|
||||
"start:dev": "NODE_ENV=development nodemon --trace-deprecation --trace-warnings --watch dist dist/server.js",
|
||||
"dev": "concurrently \"npm run build:watch\" \"npm run start:dev\"",
|
||||
|
@ -21,7 +21,6 @@
|
|||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@runtipi/common": "file:../common",
|
||||
"apollo-server-core": "^3.9.0",
|
||||
"apollo-server-express": "^3.9.0",
|
||||
"argon2": "^0.28.5",
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import AppsService from '../apps.service';
|
||||
import fs from 'fs';
|
||||
import config from '../../../config';
|
||||
import { AppConfig, FieldTypes } from '@runtipi/common';
|
||||
import childProcess from 'child_process';
|
||||
import { AppConfig, FieldTypes } from '../apps.types';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('child_process');
|
||||
|
@ -16,20 +16,20 @@ const testApp: Partial<AppConfig> = {
|
|||
id: 'test-app',
|
||||
port: 3000,
|
||||
available: true,
|
||||
form_fields: {
|
||||
test: {
|
||||
form_fields: [
|
||||
{
|
||||
type: FieldTypes.text,
|
||||
label: 'Test field',
|
||||
required: true,
|
||||
env_variable: 'TEST_FIELD',
|
||||
},
|
||||
test2: {
|
||||
{
|
||||
type: FieldTypes.text,
|
||||
label: 'Test field 2',
|
||||
required: false,
|
||||
env_variable: 'TEST_FIELD_2',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const testApp2: Partial<AppConfig> = {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { AppStatusEnum } from '@runtipi/common';
|
||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
import { Field, ObjectType, registerEnumType } from 'type-graphql';
|
||||
import { BaseEntity, Column, CreateDateColumn, Entity, UpdateDateColumn } from 'typeorm';
|
||||
import { getAppInfo } from './apps.helpers';
|
||||
import { AppInfo, AppStatusEnum } from './apps.types';
|
||||
|
||||
registerEnumType(AppStatusEnum, {
|
||||
name: 'AppStatusEnum',
|
||||
|
@ -37,6 +38,11 @@ class App extends BaseEntity {
|
|||
@Field(() => Date)
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Date;
|
||||
|
||||
@Field(() => AppInfo)
|
||||
info(): AppInfo {
|
||||
return getAppInfo(this.id);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import portUsed from 'tcp-port-used';
|
||||
import p from 'p-iteration';
|
||||
import { AppConfig } from '@runtipi/common';
|
||||
import { fileExists, readdirSync, readFile, readJsonFile, runScript, writeFile } from '../fs/fs.helpers';
|
||||
import InternalIp from 'internal-ip';
|
||||
import config from '../../config';
|
||||
import { AppInfo } from './apps.types';
|
||||
|
||||
type AppsState = { installed: string };
|
||||
|
||||
export const checkAppRequirements = async (appName: string) => {
|
||||
let valid = true;
|
||||
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`);
|
||||
const configFile: AppInfo = readJsonFile(`/apps/${appName}/config.json`);
|
||||
|
||||
if (configFile.requirements?.ports) {
|
||||
await p.forEachSeries(configFile.requirements.ports, async (port: number) => {
|
||||
if (configFile?.requirements?.ports) {
|
||||
await p.forEachSeries(configFile?.requirements.ports, async (port: number) => {
|
||||
const ip = await InternalIp.v4();
|
||||
const used = await portUsed.check(port, ip);
|
||||
|
||||
|
@ -37,10 +37,10 @@ export const getEnvMap = (appName: string): Map<string, string> => {
|
|||
};
|
||||
|
||||
export const checkEnvFile = (appName: string) => {
|
||||
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`);
|
||||
const configFile: AppInfo = readJsonFile(`/apps/${appName}/config.json`);
|
||||
const envMap = getEnvMap(appName);
|
||||
|
||||
configFile.form_fields.forEach((field) => {
|
||||
configFile.form_fields?.forEach((field) => {
|
||||
const envVar = field.env_variable;
|
||||
const envVarValue = envMap.get(envVar);
|
||||
|
||||
|
@ -88,11 +88,11 @@ export const ensureAppState = (appName: string, installed: boolean) => {
|
|||
};
|
||||
|
||||
export const generateEnvFile = (appName: string, form: Record<string, string>) => {
|
||||
const configFile: AppConfig = readJsonFile(`/apps/${appName}/config.json`);
|
||||
const configFile: AppInfo = readJsonFile(`/apps/${appName}/config.json`);
|
||||
const baseEnvFile = readFile('/.env').toString();
|
||||
let envFile = `${baseEnvFile}\nAPP_PORT=${configFile.port}\n`;
|
||||
|
||||
configFile.form_fields.forEach((field) => {
|
||||
configFile.form_fields?.forEach((field) => {
|
||||
const formValue = form[field.env_variable];
|
||||
|
||||
if (formValue) {
|
||||
|
@ -117,7 +117,7 @@ export const getAvailableApps = (): string[] => {
|
|||
|
||||
appsDir.forEach((app) => {
|
||||
if (fileExists(`/apps/${app}/config.json`)) {
|
||||
const configFile: AppConfig = readJsonFile(`/apps/${app}/config.json`);
|
||||
const configFile: AppInfo = readJsonFile(`/apps/${app}/config.json`);
|
||||
|
||||
if (configFile.available) {
|
||||
apps.push(app);
|
||||
|
@ -127,3 +127,18 @@ export const getAvailableApps = (): string[] => {
|
|||
|
||||
return apps;
|
||||
};
|
||||
|
||||
export const getAppInfo = (id: string): AppInfo => {
|
||||
try {
|
||||
const configFile: AppInfo = readJsonFile(`/apps/${id}/config.json`);
|
||||
|
||||
const state = getStateFile();
|
||||
const installed: string[] = state.installed.split(' ').filter(Boolean);
|
||||
configFile.installed = installed.includes(id);
|
||||
configFile.description = readFile(`/apps/${id}/metadata/description.md`);
|
||||
|
||||
return configFile;
|
||||
} catch (e) {
|
||||
throw new Error(`App ${id} not found`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Arg, Authorized, Mutation, Query, Resolver } from 'type-graphql';
|
||||
import AppsService from './apps.service';
|
||||
import { AppInputType, AppResponse, ListAppsResonse } from './apps.types';
|
||||
import { AppInputType, ListAppsResonse } from './apps.types';
|
||||
import App from './app.entity';
|
||||
|
||||
@Resolver()
|
||||
|
@ -10,8 +10,8 @@ export default class AppsResolver {
|
|||
return AppsService.listApps();
|
||||
}
|
||||
|
||||
@Query(() => AppResponse)
|
||||
getApp(@Arg('id', () => String) id: string): Promise<AppResponse> {
|
||||
@Query(() => App)
|
||||
getApp(@Arg('id', () => String) id: string): Promise<App> {
|
||||
return AppsService.getApp(id);
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ export default class AppsResolver {
|
|||
|
||||
@Authorized()
|
||||
@Mutation(() => App)
|
||||
async uninstallApp(@Arg('id', () => String) id: string): Promise<boolean> {
|
||||
async uninstallApp(@Arg('id', () => String) id: string): Promise<App> {
|
||||
return AppsService.uninstallApp(id);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { AppStatusEnum } from '@runtipi/common';
|
||||
import { createFolder, readFile, readJsonFile } from '../fs/fs.helpers';
|
||||
import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps, getStateFile, runAppScript } from './apps.helpers';
|
||||
import { AppInfo, AppResponse, ListAppsResonse } from './apps.types';
|
||||
import { AppInfo, AppStatusEnum, ListAppsResonse } from './apps.types';
|
||||
import App from './app.entity';
|
||||
|
||||
const startApp = async (appName: string): Promise<App> => {
|
||||
|
@ -77,17 +76,6 @@ const listApps = async (): Promise<ListAppsResonse> => {
|
|||
return { apps, total: apps.length };
|
||||
};
|
||||
|
||||
const getAppInfo = (id: string): AppInfo => {
|
||||
const configFile: AppInfo = readJsonFile(`/apps/${id}/config.json`);
|
||||
|
||||
const state = getStateFile();
|
||||
const installed: string[] = state.installed.split(' ').filter(Boolean);
|
||||
configFile.installed = installed.includes(id);
|
||||
configFile.description = readFile(`/apps/${id}/metadata/description.md`);
|
||||
|
||||
return configFile;
|
||||
};
|
||||
|
||||
const updateAppConfig = async (id: string, form: Record<string, string>): Promise<App> => {
|
||||
let app = await App.findOne({ where: { id } });
|
||||
|
||||
|
@ -114,7 +102,7 @@ const stopApp = async (id: string): Promise<App> => {
|
|||
return app;
|
||||
};
|
||||
|
||||
const uninstallApp = async (id: string): Promise<boolean> => {
|
||||
const uninstallApp = async (id: string): Promise<App> => {
|
||||
let app = await App.findOne({ where: { id } });
|
||||
|
||||
if (!app) {
|
||||
|
@ -129,16 +117,17 @@ const uninstallApp = async (id: string): Promise<boolean> => {
|
|||
await runAppScript(['uninstall', id]);
|
||||
await App.delete({ id });
|
||||
|
||||
return true;
|
||||
return { id, status: AppStatusEnum.MISSING, config: {} } as App;
|
||||
};
|
||||
|
||||
const getApp = async (id: string): Promise<AppResponse> => {
|
||||
const app = await App.findOne({ where: { id } });
|
||||
const getApp = async (id: string): Promise<App> => {
|
||||
let app = await App.findOne({ where: { id } });
|
||||
|
||||
return {
|
||||
info: getAppInfo(id),
|
||||
app,
|
||||
};
|
||||
if (!app) {
|
||||
app = { id, status: AppStatusEnum.MISSING, config: {} } as App;
|
||||
}
|
||||
|
||||
return app;
|
||||
};
|
||||
|
||||
export default { installApp, startApp, listApps, getApp, updateAppConfig, stopApp, uninstallApp };
|
||||
|
|
|
@ -1,7 +1,40 @@
|
|||
import { AppCategoriesEnum, FieldTypes } from '@runtipi/common';
|
||||
import { Field, InputType, ObjectType, registerEnumType } from 'type-graphql';
|
||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
import App from './app.entity';
|
||||
|
||||
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',
|
||||
}
|
||||
|
||||
export enum AppStatusEnum {
|
||||
RUNNING = 'running',
|
||||
STOPPED = 'stopped',
|
||||
INSTALLING = 'installing',
|
||||
UNINSTALLING = 'uninstalling',
|
||||
STOPPING = 'stopping',
|
||||
STARTING = 'starting',
|
||||
MISSING = 'missing',
|
||||
}
|
||||
|
||||
registerEnumType(AppCategoriesEnum, {
|
||||
name: 'AppCategoriesEnum',
|
||||
|
@ -35,6 +68,12 @@ class FormField {
|
|||
env_variable!: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class Requirements {
|
||||
@Field(() => [Number], { nullable: true })
|
||||
ports?: number[];
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class AppInfo {
|
||||
@Field(() => String)
|
||||
|
@ -78,6 +117,9 @@ class AppInfo {
|
|||
|
||||
@Field(() => [FormField])
|
||||
form_fields?: FormField[];
|
||||
|
||||
@Field(() => GraphQLJSONObject, { nullable: true })
|
||||
requirements?: Requirements;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
|
@ -89,15 +131,6 @@ class ListAppsResonse {
|
|||
total!: number;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
class AppResponse {
|
||||
@Field(() => App, { nullable: true })
|
||||
app!: App | null;
|
||||
|
||||
@Field(() => AppInfo)
|
||||
info!: AppInfo;
|
||||
}
|
||||
|
||||
@InputType()
|
||||
class AppInputType {
|
||||
@Field(() => String)
|
||||
|
@ -107,4 +140,4 @@ class AppInputType {
|
|||
form!: Record<string, string>;
|
||||
}
|
||||
|
||||
export { ListAppsResonse, AppInfo, AppInputType, AppResponse };
|
||||
export { ListAppsResonse, AppInfo, AppInputType };
|
||||
|
|
|
@ -10,6 +10,7 @@ import { createServer } from 'http';
|
|||
import logger from './config/logger/logger';
|
||||
import getSessionMiddleware from './core/middlewares/sessionMiddleware';
|
||||
import { MyContext } from './types';
|
||||
import { __prod__ } from './config/constants/constants';
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
|
@ -26,10 +27,16 @@ const main = async () => {
|
|||
|
||||
const httpServer = createServer(app);
|
||||
|
||||
const plugins = [ApolloLogs];
|
||||
|
||||
if (__prod__) {
|
||||
plugins.push(Playground({ settings: { 'request.credentials': 'include' } }));
|
||||
}
|
||||
|
||||
const apolloServer = new ApolloServer({
|
||||
schema,
|
||||
context: ({ req, res }): MyContext => ({ req, res }),
|
||||
plugins: [Playground({ settings: { 'request.credentials': 'include' } }), ApolloLogs],
|
||||
plugins,
|
||||
});
|
||||
await apolloServer.start();
|
||||
apolloServer.applyMiddleware({ app });
|
||||
|
|
Loading…
Add table
Reference in a new issue