From 123aaee235e900e0a380b5856e78a65253da28c8 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Thu, 2 Jun 2022 22:12:51 +0200 Subject: [PATCH] WIP - Common package --- .dockerignore | 4 + apps/adguard/config.json | 2 +- docker-compose.dev.yml | 1 + packages/common/.eslintignore | 4 + packages/common/.eslintrc.js | 18 + packages/common/.gitignore | 2 + packages/common/.npmignore | 1 + packages/common/.prettierrc.cjs | 6 + packages/common/package.json | 23 + .../common/src/constants/app.constants.ts | 13 + packages/common/src/constants/index.ts | 1 + packages/common/src/index.ts | 2 + packages/common/src/types/app.types.ts | 60 ++ packages/common/src/types/index.ts | 1 + packages/common/tsconfig.build.json | 8 + packages/common/tsconfig.json | 22 + packages/dashboard/.dockerignore | 2 +- packages/dashboard/Dockerfile.dev | 2 +- packages/dashboard/next.config.js | 7 + packages/dashboard/package.json | 5 +- .../src/components/AppTile/AppStatus.tsx | 6 +- .../src/components/AppTile/index.tsx | 2 +- .../src/components/Form/validators.ts | 2 +- packages/dashboard/src/constants/apps.ts | 123 +--- packages/dashboard/src/core/types.ts | 48 -- .../modules/Apps/components/AppActions.tsx | 8 +- .../modules/Apps/components/InstallForm.tsx | 2 +- .../modules/Apps/components/InstallModal.tsx | 2 +- .../src/modules/Apps/components/StopModal.tsx | 2 +- .../Apps/components/UninstallModal.tsx | 2 +- .../modules/Apps/components/UpdateModal.tsx | 2 +- .../modules/Apps/containers/AppDetails.tsx | 4 +- packages/dashboard/src/pages/apps/index.tsx | 16 +- packages/dashboard/src/state/appsStore.ts | 31 +- packages/system-api/package.json | 3 +- packages/system-api/src/config/types.ts | 36 - .../apps/__tests__/apps.service.test.ts | 2 +- .../src/modules/apps/apps.controller.ts | 2 +- .../src/modules/apps/apps.helpers.ts | 2 +- .../src/modules/apps/apps.service.ts | 9 +- pnpm-lock.yaml | 689 +++++++++--------- 41 files changed, 593 insertions(+), 584 deletions(-) create mode 100644 .dockerignore create mode 100644 packages/common/.eslintignore create mode 100644 packages/common/.eslintrc.js create mode 100644 packages/common/.gitignore create mode 100644 packages/common/.npmignore create mode 100644 packages/common/.prettierrc.cjs create mode 100644 packages/common/package.json create mode 100644 packages/common/src/constants/app.constants.ts create mode 100644 packages/common/src/constants/index.ts create mode 100644 packages/common/src/index.ts create mode 100644 packages/common/src/types/app.types.ts create mode 100644 packages/common/src/types/index.ts create mode 100644 packages/common/tsconfig.build.json create mode 100644 packages/common/tsconfig.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..91911d9a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +**/node_modules/ +**/.next/ +/node_modules/ +/.next/ diff --git a/apps/adguard/config.json b/apps/adguard/config.json index d251ea57..7d27d5ca 100644 --- a/apps/adguard/config.json +++ b/apps/adguard/config.json @@ -3,12 +3,12 @@ "available": true, "port": 8104, "id": "adguard", + "categories": ["network", "security"], "description": "Adguard is the best way to get rid of annoying ads and online tracking and protect your computer from malware. Make your web surfing fast, safe and ad-free.", "short_desc": "World's most advanced adblocker!", "author": "ArneNaessens", "source": "https://github.com/AdguardTeam", "image": "https://avatars.githubusercontent.com/u/8361145?s=200&v=4", - "cagegories": ["network", "security"], "requirements": { "ports": [53] }, diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 3f54b785..7f0470e7 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -37,6 +37,7 @@ services: volumes: - ${PWD}/packages/dashboard:/app - /app/node_modules + - /app/.next labels: traefik.enable: true traefik.http.routers.dashboard.rule: PathPrefix("/") # Host(`tipi.local`) && diff --git a/packages/common/.eslintignore b/packages/common/.eslintignore new file mode 100644 index 00000000..c2f476d2 --- /dev/null +++ b/packages/common/.eslintignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.cjs +dist/ diff --git a/packages/common/.eslintrc.js b/packages/common/.eslintrc.js new file mode 100644 index 00000000..4c84277e --- /dev/null +++ b/packages/common/.eslintrc.js @@ -0,0 +1,18 @@ +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' }], + }, +}; diff --git a/packages/common/.gitignore b/packages/common/.gitignore new file mode 100644 index 00000000..b9470778 --- /dev/null +++ b/packages/common/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ diff --git a/packages/common/.npmignore b/packages/common/.npmignore new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/packages/common/.npmignore @@ -0,0 +1 @@ + diff --git a/packages/common/.prettierrc.cjs b/packages/common/.prettierrc.cjs new file mode 100644 index 00000000..18502e8f --- /dev/null +++ b/packages/common/.prettierrc.cjs @@ -0,0 +1,6 @@ +module.exports = { + singleQuote: true, + semi: true, + trailingComma: 'all', + printWidth: 200, +}; diff --git a/packages/common/package.json b/packages/common/package.json new file mode 100644 index 00000000..e7094170 --- /dev/null +++ b/packages/common/package.json @@ -0,0 +1,23 @@ +{ + "name": "@runtipi/common", + "version": "0.2.7", + "main": "./dist/index.js", + "files": [ + "dist" + ], + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "tsc -b tsconfig.build.json" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "esbuild": "^0.14.38", + "typescript": "4.6.4" + }, + "dependencies": {}, + "description": "", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/common/src/constants/app.constants.ts b/packages/common/src/constants/app.constants.ts new file mode 100644 index 00000000..0625c41e --- /dev/null +++ b/packages/common/src/constants/app.constants.ts @@ -0,0 +1,13 @@ +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' }, +]; diff --git a/packages/common/src/constants/index.ts b/packages/common/src/constants/index.ts new file mode 100644 index 00000000..cd95e50a --- /dev/null +++ b/packages/common/src/constants/index.ts @@ -0,0 +1 @@ +export * from './app.constants'; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts new file mode 100644 index 00000000..33c8572a --- /dev/null +++ b/packages/common/src/index.ts @@ -0,0 +1,2 @@ +export * from './types'; +export * from './constants'; diff --git a/packages/common/src/types/app.types.ts b/packages/common/src/types/app.types.ts new file mode 100644 index 00000000..c74e0953 --- /dev/null +++ b/packages/common/src/types/app.types.ts @@ -0,0 +1,60 @@ +export enum AppCategoriesEnum { + NETWORK = 'network', + MEDIA = 'media', + DEVELOPMENT = 'development', + AUTOMATION = 'automation', + SOCIAL = 'social', + UTILITIES = 'utilities', + PHOTOGRAPHY = 'photography', + SECURITY = 'security', +} + +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; + short_desc: string; + author: string; + source: string; + installed: boolean; + categories: AppCategoriesEnum[]; + status: AppStatusEnum; +} diff --git a/packages/common/src/types/index.ts b/packages/common/src/types/index.ts new file mode 100644 index 00000000..3758b427 --- /dev/null +++ b/packages/common/src/types/index.ts @@ -0,0 +1 @@ +export * from './app.types'; diff --git a/packages/common/tsconfig.build.json b/packages/common/tsconfig.build.json new file mode 100644 index 00000000..8ee9ef1e --- /dev/null +++ b/packages/common/tsconfig.build.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["**/*.ts", "**/*.tsx"] +} diff --git a/packages/common/tsconfig.json b/packages/common/tsconfig.json new file mode 100644 index 00000000..904627e4 --- /dev/null +++ b/packages/common/tsconfig.json @@ -0,0 +1,22 @@ +{ + "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"] +} diff --git a/packages/dashboard/.dockerignore b/packages/dashboard/.dockerignore index 22052075..57ff9714 100644 --- a/packages/dashboard/.dockerignore +++ b/packages/dashboard/.dockerignore @@ -1,2 +1,2 @@ node_modules/ -.next/ \ No newline at end of file +.next/ diff --git a/packages/dashboard/Dockerfile.dev b/packages/dashboard/Dockerfile.dev index 839143cd..e58b84de 100644 --- a/packages/dashboard/Dockerfile.dev +++ b/packages/dashboard/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:18-buster-slim +FROM node:18 WORKDIR /app diff --git a/packages/dashboard/next.config.js b/packages/dashboard/next.config.js index d2842ada..efdb715c 100644 --- a/packages/dashboard/next.config.js +++ b/packages/dashboard/next.config.js @@ -2,6 +2,13 @@ const { NODE_ENV, INTERNAL_IP } = process.env; const nextConfig = { + webpackDevMiddleware: (config) => { + config.watchOptions = { + poll: 1000, + aggregateTimeout: 300, + }; + return config; + }, reactStrictMode: true, env: { INTERNAL_IP: INTERNAL_IP, diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index e4c45372..60a3f2bc 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -9,10 +9,11 @@ "lint": "next lint" }, "dependencies": { - "@chakra-ui/react": "^2.0.2", + "@chakra-ui/react": "^2.1.2", "@emotion/react": "^11", "@emotion/styled": "^11", "@fontsource/open-sans": "^4.5.8", + "@runtipi/common": "^0.2.7", "axios": "^0.26.1", "clsx": "^1.1.1", "final-form": "^4.20.6", @@ -38,11 +39,11 @@ "@types/validator": "^13.7.2", "@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/parser": "^5.0.0", - "eslint-plugin-import": "^2.25.3", "autoprefixer": "^10.4.4", "eslint": "8.12.0", "eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-next": "12.1.4", + "eslint-plugin-import": "^2.25.3", "postcss": "^8.4.12", "tailwindcss": "^3.0.23", "typescript": "4.6.4" diff --git a/packages/dashboard/src/components/AppTile/AppStatus.tsx b/packages/dashboard/src/components/AppTile/AppStatus.tsx index e6266b73..0341562b 100644 --- a/packages/dashboard/src/components/AppTile/AppStatus.tsx +++ b/packages/dashboard/src/components/AppTile/AppStatus.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { FiPauseCircle, FiPlayCircle } from 'react-icons/fi'; -import { AppStatus as TAppStatus } from '../../core/types'; +import { AppStatusEnum } from '@runtipi/common'; -const AppStatus: React.FC<{ status: TAppStatus }> = ({ status }) => { - if (status === 'running') { +const AppStatus: React.FC<{ status: AppStatusEnum }> = ({ status }) => { + if (status === AppStatusEnum.RUNNING) { return ( <> diff --git a/packages/dashboard/src/components/AppTile/index.tsx b/packages/dashboard/src/components/AppTile/index.tsx index b0b93de2..1a9343e4 100644 --- a/packages/dashboard/src/components/AppTile/index.tsx +++ b/packages/dashboard/src/components/AppTile/index.tsx @@ -2,7 +2,7 @@ import { Box, SlideFade, Image, useColorModeValue } from '@chakra-ui/react'; import Link from 'next/link'; import React from 'react'; import { FiChevronRight } from 'react-icons/fi'; -import { AppConfig } from '../../core/types'; +import { AppConfig } from '@runtipi/common'; import AppStatus from './AppStatus'; const AppTile: React.FC<{ app: AppConfig }> = ({ app }) => { diff --git a/packages/dashboard/src/components/Form/validators.ts b/packages/dashboard/src/components/Form/validators.ts index cdba3603..09189155 100644 --- a/packages/dashboard/src/components/Form/validators.ts +++ b/packages/dashboard/src/components/Form/validators.ts @@ -1,5 +1,5 @@ import validator from 'validator'; -import { AppConfig, FieldTypes } from '../../core/types'; +import { AppConfig, FieldTypes } from '@runtipi/common'; const validateField = (field: AppConfig['form_fields'][0], value: string): string | undefined => { if (field.required && !value) { diff --git a/packages/dashboard/src/constants/apps.ts b/packages/dashboard/src/constants/apps.ts index 894ebc73..da56fd8d 100644 --- a/packages/dashboard/src/constants/apps.ts +++ b/packages/dashboard/src/constants/apps.ts @@ -1,107 +1,22 @@ -import validator from 'validator'; +// import validator from 'validator'; -interface IFormField { - name: string; - type: string; - required: boolean; - description?: string; - placeholder?: string; - validate?: (value: string) => boolean; -} +// interface IFormField { +// name: string; +// type: string; +// required: boolean; +// description?: string; +// placeholder?: string; +// validate?: (value: string) => boolean; +// } -interface IAppConfig { - id: string; - name: string; - description: string; - logo: string; - url: string; - color: string; - install_form: { fields: IFormField[] }; -} +// interface IAppConfig { +// id: string; +// name: string; +// description: string; +// logo: string; +// url: string; +// color: string; +// install_form: { fields: IFormField[] }; +// } -const APP_ANONADDY: IAppConfig = { - id: 'anonaddy', - name: 'Anonaddy', - description: 'Create Unlimited Email Aliases For Free', - url: 'https://anonaddy.com/', - color: '#00a8ff', - logo: 'https://anonaddy.com/favicon.ico', - install_form: { - fields: [ - { - name: 'API Key', - type: 'text', - placeholder: 'API Key', - required: true, - validate: (value: string) => validator.isBase64(value), - }, - { - name: 'Return Path', - type: 'text', - description: 'The email address that bounces will be sent to', - placeholder: 'Return Path', - required: false, - validate: (value: string) => validator.isEmail(value), - }, - { - name: 'Admin Username', - type: 'text', - description: 'The username of the admin user', - placeholder: 'Admin Username', - required: true, - }, - { - name: 'Enable Registration', - type: 'boolean', - description: 'Allow users to register', - placeholder: 'Enable Registration', - required: false, - }, - { - name: 'Domain', - type: 'text', - description: 'The domain that will be used for the email address', - placeholder: 'Domain', - required: true, - validate: (value: string) => validator.isFQDN(value), - }, - { - name: 'Hostname', - type: 'text', - description: 'The hostname that will be used for the email address', - placeholder: 'Hostname', - required: true, - validate: (value: string) => validator.isFQDN(value), - }, - { - name: 'Secret', - type: 'text', - description: 'The secret that will be used for the email address', - placeholder: 'Secret', - required: true, - }, - { - name: 'From Name', - type: 'text', - description: 'The name that will be used for the email address', - placeholder: 'From Name', - required: true, - validate: (value: string) => validator.isLength(value, { min: 1, max: 64 }), - }, - { - name: 'From Address', - type: 'text', - description: 'The email address that will be used for the email address', - placeholder: 'From Address', - required: true, - validate: (value: string) => validator.isEmail(value), - }, - ], - }, -}; - -const APPS_CONFIG = { - available: [APP_ANONADDY], -}; - -export default APPS_CONFIG; +export {}; diff --git a/packages/dashboard/src/core/types.ts b/packages/dashboard/src/core/types.ts index 1c1069a9..20234f7c 100644 --- a/packages/dashboard/src/core/types.ts +++ b/packages/dashboard/src/core/types.ts @@ -1,57 +1,9 @@ -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 interface AppConfig { - id: string; - port: number; - requirements?: { - ports?: number[]; - }; - name: string; - description: string; - version: string; - image: string; - form_fields: Record; - short_desc: string; - author: string; - source: string; - installed: boolean; - status: AppStatus; -} - export enum RequestStatus { SUCCESS = 'SUCCESS', ERROR = 'ERROR', LOADING = 'LOADING', } -export enum AppStatus { - RUNNING = 'running', - STOPPED = 'stopped', - INSTALLING = 'installing', - UNINSTALLING = 'uninstalling', - STOPPING = 'stopping', - STARTING = 'starting', -} - export interface IUser { name: string; email: string; diff --git a/packages/dashboard/src/modules/Apps/components/AppActions.tsx b/packages/dashboard/src/modules/Apps/components/AppActions.tsx index 94184371..62c79264 100644 --- a/packages/dashboard/src/modules/Apps/components/AppActions.tsx +++ b/packages/dashboard/src/modules/Apps/components/AppActions.tsx @@ -1,7 +1,7 @@ import { Button } from '@chakra-ui/react'; import React from 'react'; import { FiExternalLink, FiPause, FiPlay, FiSettings, FiTrash2 } from 'react-icons/fi'; -import { AppConfig, AppStatus } from '../../../core/types'; +import { AppConfig, AppStatusEnum } from '@runtipi/common'; interface IProps { app: AppConfig; @@ -16,7 +16,7 @@ interface IProps { const AppActions: React.FC = ({ app, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate }) => { const hasSettings = Object.keys(app.form_fields).length > 0; - if (app?.installed && app.status === AppStatus.STOPPED) { + if (app?.installed && app.status === AppStatusEnum.STOPPED) { return (
); - } else if (app?.installed && app.status === AppStatus.RUNNING) { + } else if (app?.installed && app.status === AppStatusEnum.RUNNING) { return (
); - } else if (app.status === AppStatus.INSTALLING || app.status === AppStatus.UNINSTALLING || app.status === AppStatus.STARTING || app.status === AppStatus.STOPPING) { + } else if (app.status === AppStatusEnum.INSTALLING || app.status === AppStatusEnum.UNINSTALLING || app.status === AppStatusEnum.STARTING || app.status === AppStatusEnum.STOPPING) { return (