Sfoglia il codice sorgente

refactor: removal and replace usage of old graphql generated types

Nicolas Meienberger 2 anni fa
parent
commit
d72526ab8a
32 ha cambiato i file con 191 aggiunte e 947 eliminazioni
  1. 39 0
      packages/dashboard/server.ts
  2. 4 4
      packages/dashboard/src/client/components/AppStatus/AppStatus.tsx
  3. 1 1
      packages/dashboard/src/client/components/AppTile/AppTile.tsx
  4. 0 681
      packages/dashboard/src/client/generated/graphql.tsx
  5. 0 7
      packages/dashboard/src/client/graphql/mutations/installApp.graphql
  6. 0 7
      packages/dashboard/src/client/graphql/mutations/startApp.graphql
  7. 0 7
      packages/dashboard/src/client/graphql/mutations/stopApp.graphql
  8. 0 7
      packages/dashboard/src/client/graphql/mutations/unintallApp.graphql
  9. 0 7
      packages/dashboard/src/client/graphql/mutations/updateApp.graphql
  10. 0 7
      packages/dashboard/src/client/graphql/mutations/updateAppConfig.graphql
  11. 0 42
      packages/dashboard/src/client/graphql/queries/getApp.graphql
  12. 0 21
      packages/dashboard/src/client/graphql/queries/installedApps.graphql
  13. 0 18
      packages/dashboard/src/client/graphql/queries/listApps.graphql
  14. 3 3
      packages/dashboard/src/client/modules/AppStore/components/AppStoreTile/AppStoreTile.tsx
  15. 4 4
      packages/dashboard/src/client/modules/AppStore/components/CategorySelector/CategorySelector.tsx
  16. 17 17
      packages/dashboard/src/client/modules/AppStore/helpers/table.helpers.ts
  17. 1 1
      packages/dashboard/src/client/modules/AppStore/helpers/table.types.ts
  18. 3 3
      packages/dashboard/src/client/modules/AppStore/state/appStoreState.ts
  19. 9 10
      packages/dashboard/src/client/modules/Apps/components/AppActions/AppActions.test.tsx
  20. 15 14
      packages/dashboard/src/client/modules/Apps/components/AppActions/AppActions.tsx
  21. 1 1
      packages/dashboard/src/client/modules/Apps/components/AppDetailsTabs.tsx
  22. 10 10
      packages/dashboard/src/client/modules/Apps/components/InstallForm/InstallForm.test.tsx
  23. 4 4
      packages/dashboard/src/client/modules/Apps/components/InstallForm/InstallForm.tsx
  24. 8 8
      packages/dashboard/src/client/modules/Apps/components/InstallModal/InstallModal.test.tsx
  25. 7 6
      packages/dashboard/src/client/modules/Apps/components/InstallModal/InstallModal.tsx
  26. 5 5
      packages/dashboard/src/client/modules/Apps/components/StopModal.tsx
  27. 4 5
      packages/dashboard/src/client/modules/Apps/components/UninstallModal.tsx
  28. 4 4
      packages/dashboard/src/client/modules/Apps/components/UpdateModal/UpdateModal.test.tsx
  29. 5 6
      packages/dashboard/src/client/modules/Apps/components/UpdateModal/UpdateModal.tsx
  30. 8 7
      packages/dashboard/src/client/modules/Apps/components/UpdateSettingsModal.tsx
  31. 30 21
      packages/dashboard/src/client/modules/Apps/utils/validators/validators.test.tsx
  32. 9 9
      packages/dashboard/src/client/modules/Apps/utils/validators/validators.ts

+ 39 - 0
packages/dashboard/server.ts

@@ -0,0 +1,39 @@
+/* eslint-disable consistent-return */
+import { createServer } from 'http';
+import { parse } from 'url';
+import next from 'next';
+import { EventDispatcher } from './src/server/core/EventDispatcher';
+import { getConfig, setConfig } from './src/server/core/TipiConfig';
+import { Logger } from './src/server/core/Logger';
+import { runPostgresMigrations } from './run-migration';
+import { AppServiceClass } from './src/server/services/apps/apps.service';
+import { prisma } from './src/server/db/client';
+
+const port = parseInt(process.env.PORT || '3000', 10);
+const dev = process.env.NODE_ENV !== 'production';
+const nextApp = next({ dev });
+const handle = nextApp.getRequestHandler();
+
+nextApp.prepare().then(async () => {
+  createServer(async (req, res) => {
+    const parsedUrl = parse(req.url!, true);
+
+    handle(req, res, parsedUrl);
+  }).listen(port);
+
+  const appService = new AppServiceClass(prisma);
+  EventDispatcher.clear();
+
+  // Run database migrations
+  await runPostgresMigrations();
+
+  // startJobs();
+  setConfig('status', 'RUNNING');
+
+  await EventDispatcher.dispatchEventAsync('clone_repo', [getConfig().appsRepoUrl]);
+  await EventDispatcher.dispatchEventAsync('update_repo', [getConfig().appsRepoUrl]);
+
+  appService.startAllApps();
+
+  Logger.info(`> Server listening at http://localhost:${port} as ${dev ? 'development' : process.env.NODE_ENV}`);
+});

+ 4 - 4
packages/dashboard/src/client/components/AppStatus/AppStatus.tsx

@@ -1,14 +1,14 @@
 import clsx from 'clsx';
 import React from 'react';
+import * as AppTypes from '../../core/types';
 import styles from './AppStatus.module.scss';
-import { AppStatusEnum } from '../../generated/graphql';
 
-export const AppStatus: React.FC<{ status: AppStatusEnum; lite?: boolean }> = ({ status, lite }) => {
+export const AppStatus: React.FC<{ status: AppTypes.AppStatus; lite?: boolean }> = ({ status, lite }) => {
   const formattedStatus = `${status[0]}${status.substring(1, status.length).toLowerCase()}`;
 
   const classes = clsx('status-dot status-gray', {
-    'status-dot-animated status-green': status === AppStatusEnum.Running,
-    'status-red': status === AppStatusEnum.Stopped,
+    'status-dot-animated status-green': status === 'running',
+    'status-red': status === 'stopped',
   });
 
   return (

+ 1 - 1
packages/dashboard/src/client/components/AppTile/AppTile.tsx

@@ -4,8 +4,8 @@ import { IconDownload } from '@tabler/icons';
 import { AppStatus } from '../AppStatus';
 import { AppLogo } from '../AppLogo/AppLogo';
 import { limitText } from '../../modules/AppStore/helpers/table.helpers';
-import { AppInfo, AppStatusEnum } from '../../generated/graphql';
 import styles from './AppTile.module.scss';
+import { AppInfo, AppStatus as AppStatusEnum } from '../../core/types';
 
 type AppTileInfo = Pick<AppInfo, 'id' | 'name' | 'description' | 'short_desc'>;
 

+ 0 - 681
packages/dashboard/src/client/generated/graphql.tsx

@@ -1,681 +0,0 @@
-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'];
-  domain?: Maybe<Scalars['String']>;
-  exposed: Scalars['Boolean'];
-  id: Scalars['String'];
-  info?: Maybe<AppInfo>;
-  lastOpened: Scalars['DateTime'];
-  numOpened: Scalars['Float'];
-  status: AppStatusEnum;
-  updateInfo?: Maybe<UpdateInfo>;
-  updatedAt: Scalars['DateTime'];
-  version?: Maybe<Scalars['Float']>;
-};
-
-export enum AppCategoriesEnum {
-  Automation = 'AUTOMATION',
-  Books = 'BOOKS',
-  Data = 'DATA',
-  Development = 'DEVELOPMENT',
-  Featured = 'FEATURED',
-  Finance = 'FINANCE',
-  Gaming = 'GAMING',
-  Media = 'MEDIA',
-  Music = 'MUSIC',
-  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'];
-  exposable?: Maybe<Scalars['Boolean']>;
-  form_fields: Array<FormField>;
-  https?: Maybe<Scalars['Boolean']>;
-  id: Scalars['String'];
-  name: Scalars['String'];
-  no_gui?: Maybe<Scalars['Boolean']>;
-  port: Scalars['Float'];
-  short_desc: Scalars['String'];
-  source: Scalars['String'];
-  supported_architectures?: Maybe<Array<AppSupportedArchitecturesEnum>>;
-  tipi_version: Scalars['Float'];
-  url_suffix?: Maybe<Scalars['String']>;
-  version?: Maybe<Scalars['String']>;
-};
-
-export type AppInputType = {
-  domain: Scalars['String'];
-  exposed: Scalars['Boolean'];
-  form: Scalars['JSONObject'];
-  id: Scalars['String'];
-};
-
-export enum AppStatusEnum {
-  Installing = 'INSTALLING',
-  Missing = 'MISSING',
-  Running = 'RUNNING',
-  Starting = 'STARTING',
-  Stopped = 'STOPPED',
-  Stopping = 'STOPPING',
-  Uninstalling = 'UNINSTALLING',
-  Updating = 'UPDATING',
-}
-
-export enum AppSupportedArchitecturesEnum {
-  Amd64 = 'AMD64',
-  Arm = 'ARM',
-  Arm64 = 'ARM64',
-}
-
-export enum FieldTypesEnum {
-  Email = 'email',
-  Fqdn = 'fqdn',
-  Fqdnip = 'fqdnip',
-  Ip = 'ip',
-  Number = 'number',
-  Password = 'password',
-  Random = 'random',
-  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']>;
-  placeholder?: Maybe<Scalars['String']>;
-  required?: Maybe<Scalars['Boolean']>;
-  type: FieldTypesEnum;
-};
-
-export type ListAppsResonse = {
-  __typename?: 'ListAppsResonse';
-  apps: Array<AppInfo>;
-  total: Scalars['Float'];
-};
-
-export type Mutation = {
-  __typename?: 'Mutation';
-  installApp: App;
-  startApp: App;
-  stopApp: App;
-  uninstallApp: App;
-  updateApp: App;
-  updateAppConfig: App;
-};
-
-export type MutationInstallAppArgs = {
-  input: AppInputType;
-};
-
-export type MutationStartAppArgs = {
-  id: Scalars['String'];
-};
-
-export type MutationStopAppArgs = {
-  id: Scalars['String'];
-};
-
-export type MutationUninstallAppArgs = {
-  id: Scalars['String'];
-};
-
-export type MutationUpdateAppArgs = {
-  id: Scalars['String'];
-};
-
-export type MutationUpdateAppConfigArgs = {
-  input: AppInputType;
-};
-
-export type Query = {
-  __typename?: 'Query';
-  getApp: App;
-  installedApps: Array<App>;
-  listAppsInfo: ListAppsResonse;
-};
-
-export type QueryGetAppArgs = {
-  id: Scalars['String'];
-};
-
-export type UpdateInfo = {
-  __typename?: 'UpdateInfo';
-  current: Scalars['Float'];
-  dockerVersion?: Maybe<Scalars['String']>;
-  latest: Scalars['Float'];
-};
-
-export type InstallAppMutationVariables = Exact<{
-  input: AppInputType;
-}>;
-
-export type InstallAppMutation = { __typename?: 'Mutation'; installApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
-
-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 UpdateAppMutationVariables = Exact<{
-  id: Scalars['String'];
-}>;
-
-export type UpdateAppMutation = { __typename?: 'Mutation'; updateApp: { __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;
-    version?: number | null;
-    exposed: boolean;
-    domain?: string | null;
-    updateInfo?: { __typename?: 'UpdateInfo'; current: number; latest: number; dockerVersion?: string | null } | null;
-    info?: {
-      __typename?: 'AppInfo';
-      id: string;
-      port: number;
-      name: string;
-      description: string;
-      available: boolean;
-      version?: string | null;
-      tipi_version: number;
-      short_desc: string;
-      author: string;
-      source: string;
-      categories: Array<AppCategoriesEnum>;
-      url_suffix?: string | null;
-      https?: boolean | null;
-      exposable?: boolean | null;
-      no_gui?: boolean | null;
-      form_fields: Array<{
-        __typename?: 'FormField';
-        type: FieldTypesEnum;
-        label: string;
-        max?: number | null;
-        min?: number | null;
-        hint?: string | null;
-        placeholder?: string | null;
-        required?: boolean | null;
-        env_variable: string;
-      }>;
-    } | null;
-  };
-};
-
-export type InstalledAppsQueryVariables = Exact<{ [key: string]: never }>;
-
-export type InstalledAppsQuery = {
-  __typename?: 'Query';
-  installedApps: Array<{
-    __typename?: 'App';
-    id: string;
-    status: AppStatusEnum;
-    config: any;
-    version?: number | null;
-    updateInfo?: { __typename?: 'UpdateInfo'; current: number; latest: number; dockerVersion?: string | null } | null;
-    info?: { __typename?: 'AppInfo'; id: string; name: string; description: string; tipi_version: number; short_desc: string; https?: boolean | null } | null;
-  }>;
-};
-
-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;
-      tipi_version: number;
-      port: number;
-      name: string;
-      version?: string | null;
-      short_desc: string;
-      author: string;
-      categories: Array<AppCategoriesEnum>;
-      https?: boolean | 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 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 UpdateAppDocument = gql`
-  mutation UpdateApp($id: String!) {
-    updateApp(id: $id) {
-      id
-      status
-      __typename
-    }
-  }
-`;
-export type UpdateAppMutationFn = Apollo.MutationFunction<UpdateAppMutation, UpdateAppMutationVariables>;
-
-/**
- * __useUpdateAppMutation__
- *
- * To run a mutation, you first call `useUpdateAppMutation` within a React component and pass it any options that fit your needs.
- * When your component renders, `useUpdateAppMutation` 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 [updateAppMutation, { data, loading, error }] = useUpdateAppMutation({
- *   variables: {
- *      id: // value for 'id'
- *   },
- * });
- */
-export function useUpdateAppMutation(baseOptions?: Apollo.MutationHookOptions<UpdateAppMutation, UpdateAppMutationVariables>) {
-  const options = { ...defaultOptions, ...baseOptions };
-  return Apollo.useMutation<UpdateAppMutation, UpdateAppMutationVariables>(UpdateAppDocument, options);
-}
-export type UpdateAppMutationHookResult = ReturnType<typeof useUpdateAppMutation>;
-export type UpdateAppMutationResult = Apollo.MutationResult<UpdateAppMutation>;
-export type UpdateAppMutationOptions = Apollo.BaseMutationOptions<UpdateAppMutation, UpdateAppMutationVariables>;
-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
-      version
-      exposed
-      domain
-      updateInfo {
-        current
-        latest
-        dockerVersion
-      }
-      info {
-        id
-        port
-        name
-        description
-        available
-        version
-        tipi_version
-        short_desc
-        author
-        source
-        categories
-        url_suffix
-        https
-        exposable
-        no_gui
-        form_fields {
-          type
-          label
-          max
-          min
-          hint
-          placeholder
-          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
-      version
-      updateInfo {
-        current
-        latest
-        dockerVersion
-      }
-      info {
-        id
-        name
-        description
-        tipi_version
-        short_desc
-        https
-      }
-    }
-  }
-`;
-
-/**
- * __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 ListAppsDocument = gql`
-  query ListApps {
-    listAppsInfo {
-      apps {
-        id
-        available
-        tipi_version
-        port
-        name
-        version
-        short_desc
-        author
-        categories
-        https
-      }
-      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>;

+ 0 - 7
packages/dashboard/src/client/graphql/mutations/installApp.graphql

@@ -1,7 +0,0 @@
-mutation InstallApp($input: AppInputType!) {
-  installApp(input: $input) {
-    id
-    status
-    __typename
-  }
-}

+ 0 - 7
packages/dashboard/src/client/graphql/mutations/startApp.graphql

@@ -1,7 +0,0 @@
-mutation StartApp($id: String!) {
-  startApp(id: $id) {
-    id
-    status
-    __typename
-  }
-}

+ 0 - 7
packages/dashboard/src/client/graphql/mutations/stopApp.graphql

@@ -1,7 +0,0 @@
-mutation StopApp($id: String!) {
-  stopApp(id: $id) {
-    id
-    status
-    __typename
-  }
-}

+ 0 - 7
packages/dashboard/src/client/graphql/mutations/unintallApp.graphql

@@ -1,7 +0,0 @@
-mutation UninstallApp($id: String!) {
-  uninstallApp(id: $id) {
-    id
-    status
-    __typename
-  }
-}

+ 0 - 7
packages/dashboard/src/client/graphql/mutations/updateApp.graphql

@@ -1,7 +0,0 @@
-mutation UpdateApp($id: String!) {
-  updateApp(id: $id) {
-    id
-    status
-    __typename
-  }
-}

+ 0 - 7
packages/dashboard/src/client/graphql/mutations/updateAppConfig.graphql

@@ -1,7 +0,0 @@
-mutation UpdateAppConfig($input: AppInputType!) {
-  updateAppConfig(input: $input) {
-    id
-    status
-    __typename
-  }
-}

+ 0 - 42
packages/dashboard/src/client/graphql/queries/getApp.graphql

@@ -1,42 +0,0 @@
-query GetApp($appId: String!) {
-  getApp(id: $appId) {
-    id
-    status
-    config
-    version
-    exposed
-    domain
-    updateInfo {
-      current
-      latest
-      dockerVersion
-    }
-    info {
-      id
-      port
-      name
-      description
-      available
-      version
-      tipi_version
-      short_desc
-      author
-      source
-      categories
-      url_suffix
-      https
-      exposable
-      no_gui
-      form_fields {
-        type
-        label
-        max
-        min
-        hint
-        placeholder
-        required
-        env_variable
-      }
-    }
-  }
-}

+ 0 - 21
packages/dashboard/src/client/graphql/queries/installedApps.graphql

@@ -1,21 +0,0 @@
-query InstalledApps {
-  installedApps {
-    id
-    status
-    config
-    version
-    updateInfo {
-      current
-      latest
-      dockerVersion
-    }
-    info {
-      id
-      name
-      description
-      tipi_version
-      short_desc
-      https
-    }
-  }
-}

+ 0 - 18
packages/dashboard/src/client/graphql/queries/listApps.graphql

@@ -1,18 +0,0 @@
-# Write your query or mutation here
-query ListApps {
-  listAppsInfo {
-    apps {
-      id
-      available
-      tipi_version
-      port
-      name
-      version
-      short_desc
-      author
-      categories
-      https
-    }
-    total
-  }
-}

+ 3 - 3
packages/dashboard/src/client/modules/AppStore/components/AppStoreTile/AppStoreTile.tsx

@@ -2,14 +2,14 @@ import clsx from 'clsx';
 import Link from 'next/link';
 import React from 'react';
 import { AppLogo } from '../../../../components/AppLogo/AppLogo';
-import { AppCategoriesEnum } from '../../../../generated/graphql';
+import { AppCategory } from '../../../../core/types';
 import { colorSchemeForCategory, limitText } from '../../helpers/table.helpers';
 import styles from './AppStoreTile.module.scss';
 
 type App = {
   id: string;
   name: string;
-  categories: string[];
+  categories: AppCategory[];
   short_desc: string;
 };
 
@@ -21,7 +21,7 @@ const AppStoreTile: React.FC<{ app: App }> = ({ app }) => (
         <h3 className="text-bold h-3 mb-2">{limitText(app.name, 20)}</h3>
         <p className="text-muted text-nowrap mb-2">{limitText(app.short_desc, 30)}</p>
         {app.categories?.map((category) => (
-          <div className={`badge me-1 bg-${colorSchemeForCategory[category as AppCategoriesEnum]}`} key={`${app.id}-${category}`}>
+          <div className={`badge me-1 bg-${colorSchemeForCategory[category]}`} key={`${app.id}-${category}`}>
             {category.toLocaleLowerCase()}
           </div>
         ))}

+ 4 - 4
packages/dashboard/src/client/modules/AppStore/components/CategorySelector/CategorySelector.tsx

@@ -1,16 +1,16 @@
 import React from 'react';
 import Select, { SingleValue } from 'react-select';
 import { APP_CATEGORIES } from '../../../../core/constants';
-import { AppCategoriesEnum } from '../../../../generated/graphql';
+import { AppCategory } from '../../../../core/types';
 import { useUIStore } from '../../../../state/uiStore';
 
 interface IProps {
-  onSelect: (value?: AppCategoriesEnum) => void;
+  onSelect: (value?: AppCategory) => void;
   className?: string;
-  initialValue?: AppCategoriesEnum;
+  initialValue?: AppCategory;
 }
 
-type OptionsType = { value: AppCategoriesEnum; label: string };
+type OptionsType = { value: AppCategory; label: string };
 
 const CategorySelector: React.FC<IProps> = ({ onSelect, className, initialValue }) => {
   const { darkMode } = useUIStore();

+ 17 - 17
packages/dashboard/src/client/modules/AppStore/helpers/table.helpers.ts

@@ -1,11 +1,11 @@
-import { AppCategoriesEnum, AppInfo } from '../../../generated/graphql';
+import { AppCategory, AppInfo } from '../../../core/types';
 import { AppTableData } from './table.types';
 
 type SortParams = {
   data: AppTableData;
   col: keyof Pick<AppInfo, 'name'>;
   direction: 'asc' | 'desc';
-  category?: AppCategoriesEnum;
+  category?: AppCategory;
   search: string;
 };
 
@@ -32,19 +32,19 @@ export const sortTable = (params: SortParams) => {
 
 export const limitText = (text: string, limit: number) => (text.length > limit ? `${text.substring(0, limit)}...` : text);
 
-export const colorSchemeForCategory: Record<AppCategoriesEnum, string> = {
-  [AppCategoriesEnum.Network]: 'blue',
-  [AppCategoriesEnum.Media]: 'azure',
-  [AppCategoriesEnum.Automation]: 'indigo',
-  [AppCategoriesEnum.Development]: 'red',
-  [AppCategoriesEnum.Utilities]: 'muted',
-  [AppCategoriesEnum.Photography]: 'purple',
-  [AppCategoriesEnum.Security]: 'organge',
-  [AppCategoriesEnum.Social]: 'yellow',
-  [AppCategoriesEnum.Featured]: 'lime',
-  [AppCategoriesEnum.Data]: 'green',
-  [AppCategoriesEnum.Books]: 'teal',
-  [AppCategoriesEnum.Music]: 'cyan',
-  [AppCategoriesEnum.Finance]: 'dark',
-  [AppCategoriesEnum.Gaming]: 'pink',
+export const colorSchemeForCategory: Record<AppCategory, string> = {
+  network: 'blue',
+  media: 'azure',
+  automation: 'indigo',
+  development: 'red',
+  utilities: 'muted',
+  photography: 'purple',
+  security: 'organge',
+  social: 'yellow',
+  featured: 'lime',
+  data: 'green',
+  books: 'teal',
+  music: 'cyan',
+  finance: 'dark',
+  gaming: 'pink',
 };

+ 1 - 1
packages/dashboard/src/client/modules/AppStore/helpers/table.types.ts

@@ -1,4 +1,4 @@
-import { AppInfo } from '../../../generated/graphql';
+import { AppInfo } from '../../../core/types';
 
 export type SortableColumns = keyof Pick<AppInfo, 'name'>;
 export type SortDirection = 'asc' | 'desc';

+ 3 - 3
packages/dashboard/src/client/modules/AppStore/state/appStoreState.ts

@@ -1,12 +1,12 @@
 import create from 'zustand';
-import { AppCategoriesEnum } from '../../../generated/graphql';
+import { AppCategory } from '../../../core/types';
 import { SortableColumns } from '../helpers/table.types';
 
 type Store = {
   search: string;
   setSearch: (textSearch: string) => void;
-  category?: AppCategoriesEnum;
-  setCategory: (selectedCategories?: AppCategoriesEnum) => void;
+  category?: AppCategory;
+  setCategory: (selectedCategories?: AppCategory) => void;
   sort: SortableColumns;
   setSort: (sort: SortableColumns) => void;
   sortDirection: 'asc' | 'desc';

+ 9 - 10
packages/dashboard/src/client/modules/Apps/components/AppActions/AppActions.test.tsx

@@ -1,9 +1,8 @@
 /* eslint-disable @typescript-eslint/ban-ts-comment */
 import React from 'react';
-import '@testing-library/jest-dom/extend-expect';
 import { AppActions } from './AppActions';
-import { AppInfo, AppStatusEnum } from '../../../../generated/graphql';
 import { cleanup, fireEvent, render, screen } from '../../../../../../tests/test-utils';
+import { AppInfo } from '../../../../core/types';
 
 afterEach(cleanup);
 
@@ -19,7 +18,7 @@ describe('Test: AppActions', () => {
     const onStart = jest.fn();
     const onRemove = jest.fn();
     // @ts-expect-error
-    const { getByText } = render(<AppActions status={AppStatusEnum.Stopped} app={app} onStart={onStart} onUninstall={onRemove} />);
+    const { getByText } = render(<AppActions status="stopped" info={app} onStart={onStart} onUninstall={onRemove} />);
 
     // Act
     fireEvent.click(getByText('Start'));
@@ -34,7 +33,7 @@ describe('Test: AppActions', () => {
 
   it('should render the correct buttons when app status is running', () => {
     // @ts-expect-error
-    const { getByText } = render(<AppActions status={AppStatusEnum.Running} app={app} />);
+    const { getByText } = render(<AppActions status="running" info={app} />);
     expect(getByText('Stop')).toBeInTheDocument();
     expect(getByText('Open')).toBeInTheDocument();
     expect(getByText('Settings')).toBeInTheDocument();
@@ -42,42 +41,42 @@ describe('Test: AppActions', () => {
 
   it('should render the correct buttons when app status is starting', () => {
     // @ts-expect-error
-    render(<AppActions status={AppStatusEnum.Starting} app={app} />);
+    render(<AppActions status="starting" info={app} />);
     expect(screen.getByText('Cancel')).toBeInTheDocument();
     expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
   });
 
   it('should render the correct buttons when app status is stopping', () => {
     // @ts-expect-error
-    render(<AppActions status={AppStatusEnum.Stopping} app={app} />);
+    render(<AppActions status="stopping" info={app} />);
     expect(screen.getByText('Cancel')).toBeInTheDocument();
     expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
   });
 
   it('should render the correct buttons when app status is removing', () => {
     // @ts-expect-error
-    render(<AppActions status={AppStatusEnum.Uninstalling} app={app} />);
+    render(<AppActions status="uninstalling" info={app} />);
     expect(screen.getByText('Cancel')).toBeInTheDocument();
     expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
   });
 
   it('should render the correct buttons when app status is installing', () => {
     // @ts-ignore
-    render(<AppActions status={AppStatusEnum.Installing} app={app} />);
+    render(<AppActions status="installing" info={app} />);
     expect(screen.getByText('Cancel')).toBeInTheDocument();
     expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
   });
 
   it('should render the correct buttons when app status is updating', () => {
     // @ts-expect-error
-    render(<AppActions status={AppStatusEnum.Updating} app={app} />);
+    render(<AppActions status="updating" info={app} />);
     expect(screen.getByText('Cancel')).toBeInTheDocument();
     expect(screen.getByTestId('action-button-loading')).toBeInTheDocument();
   });
 
   it('should render the correct buttons when app status is missing', () => {
     // @ts-expect-error
-    render(<AppActions status={AppStatusEnum.Missing} app={app} />);
+    render(<AppActions status="missing" info={app} />);
     expect(screen.getByText('Install')).toBeInTheDocument();
   });
 });

+ 15 - 14
packages/dashboard/src/client/modules/Apps/components/AppActions/AppActions.tsx

@@ -1,13 +1,14 @@
 import { IconDownload, IconExternalLink, IconPlayerPause, IconPlayerPlay, IconSettings, IconTrash, IconX, TablerIcon } from '@tabler/icons';
 import clsx from 'clsx';
 import React from 'react';
+import type { AppStatus } from '../../../../../server/services/apps/apps.types';
 
 import { Button } from '../../../../components/ui/Button';
-import { AppInfo, AppStatusEnum } from '../../../../generated/graphql';
+import { AppInfo } from '../../../../core/types';
 
 interface IProps {
-  app: AppInfo;
-  status?: AppStatusEnum;
+  info: AppInfo;
+  status?: AppStatus;
   updateAvailable: boolean;
   onInstall: () => void;
   onUninstall: () => void;
@@ -39,8 +40,8 @@ const ActionButton: React.FC<BtnProps> = (props) => {
   );
 };
 
-export const AppActions: React.FC<IProps> = ({ app, status, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate, onCancel, updateAvailable, onUpdateSettings }) => {
-  const hasSettings = Object.keys(app.form_fields).length > 0 || app.exposable;
+export const AppActions: React.FC<IProps> = ({ info, status, onInstall, onUninstall, onStart, onStop, onOpen, onUpdate, onCancel, updateAvailable, onUpdateSettings }) => {
+  const hasSettings = Object.keys(info.form_fields).length > 0 || info.exposable;
 
   const buttons: JSX.Element[] = [];
 
@@ -55,7 +56,7 @@ export const AppActions: React.FC<IProps> = ({ app, status, onInstall, onUninsta
   const UpdateButton = <ActionButton key="update" Icon={IconDownload} onClick={onUpdate} width={null} title="Update" color="success" />;
 
   switch (status) {
-    case AppStatusEnum.Stopped:
+    case 'stopped':
       buttons.push(StartButton, RemoveButton);
       if (hasSettings) {
         buttons.push(SettingsButton);
@@ -64,9 +65,9 @@ export const AppActions: React.FC<IProps> = ({ app, status, onInstall, onUninsta
         buttons.push(UpdateButton);
       }
       break;
-    case AppStatusEnum.Running:
+    case 'running':
       buttons.push(StopButton);
-      if (!app.no_gui) {
+      if (!info.no_gui) {
         buttons.push(OpenButton);
       }
       if (hasSettings) {
@@ -76,14 +77,14 @@ export const AppActions: React.FC<IProps> = ({ app, status, onInstall, onUninsta
         buttons.push(UpdateButton);
       }
       break;
-    case AppStatusEnum.Installing:
-    case AppStatusEnum.Uninstalling:
-    case AppStatusEnum.Starting:
-    case AppStatusEnum.Stopping:
-    case AppStatusEnum.Updating:
+    case 'installing':
+    case 'uninstalling':
+    case 'starting':
+    case 'stopping':
+    case 'updating':
       buttons.push(LoadingButtion, CancelButton);
       break;
-    case AppStatusEnum.Missing:
+    case 'missing':
       buttons.push(InstallButton);
       break;
     default:

+ 1 - 1
packages/dashboard/src/client/modules/Apps/components/AppDetailsTabs.tsx

@@ -2,7 +2,7 @@ import { IconExternalLink } from '@tabler/icons';
 import React from 'react';
 import { DataGrid, DataGridItem } from '../../../components/ui/DataGrid';
 import Markdown from '../../../components/Markdown/Markdown';
-import { AppInfo } from '../../../generated/graphql';
+import { AppInfo } from '../../../core/types';
 
 interface IProps {
   info: AppInfo;

+ 10 - 10
packages/dashboard/src/client/modules/Apps/components/InstallForm/InstallForm.test.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import { fireEvent, render, screen, waitFor } from '../../../../../../tests/test-utils';
-import { FieldTypesEnum, FormField } from '../../../../generated/graphql';
+import { FormField } from '../../../../core/types';
 import { InstallForm } from './InstallForm';
 
 describe('Test: InstallForm', () => {
@@ -12,11 +12,11 @@ describe('Test: InstallForm', () => {
 
   it('should render fields with correct types', () => {
     const formFields: FormField[] = [
-      { env_variable: 'test', label: 'test', type: FieldTypesEnum.Text },
-      { env_variable: 'test2', label: 'test2', type: FieldTypesEnum.Password },
-      { env_variable: 'test3', label: 'test3', type: FieldTypesEnum.Email },
-      { env_variable: 'test4', label: 'test4', type: FieldTypesEnum.Url },
-      { env_variable: 'test5', label: 'test5', type: FieldTypesEnum.Number },
+      { env_variable: 'test', label: 'test', type: 'text', required: false },
+      { env_variable: 'test2', label: 'test2', type: 'password', required: false },
+      { env_variable: 'test3', label: 'test3', type: 'email', required: false },
+      { env_variable: 'test4', label: 'test4', type: 'url', required: false },
+      { env_variable: 'test5', label: 'test5', type: 'number', required: false },
     ];
 
     render(<InstallForm formFields={formFields} onSubmit={jest.fn} />);
@@ -29,7 +29,7 @@ describe('Test: InstallForm', () => {
   });
 
   it('should call submit function with correct values', async () => {
-    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: FieldTypesEnum.Text }];
+    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: false }];
 
     const onSubmit = jest.fn();
 
@@ -46,7 +46,7 @@ describe('Test: InstallForm', () => {
   });
 
   it('should show validation error when required field is empty', async () => {
-    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: FieldTypesEnum.Text, required: true }];
+    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: true }];
 
     const onSubmit = jest.fn();
 
@@ -60,7 +60,7 @@ describe('Test: InstallForm', () => {
   });
 
   it('should pre-fill fields if initialValues are provided', () => {
-    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: FieldTypesEnum.Text, required: true }];
+    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: true }];
 
     const onSubmit = jest.fn();
 
@@ -70,7 +70,7 @@ describe('Test: InstallForm', () => {
   });
 
   it('should render expose switch when app is exposable', () => {
-    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: FieldTypesEnum.Text, required: true }];
+    const formFields: FormField[] = [{ env_variable: 'test-env', label: 'test-field', type: 'text', required: true }];
 
     const onSubmit = jest.fn();
 

+ 4 - 4
packages/dashboard/src/client/modules/Apps/components/InstallForm/InstallForm.tsx

@@ -1,16 +1,16 @@
 import React, { useEffect } from 'react';
 import { useForm } from 'react-hook-form';
 
-import { AppInfo, FormField } from '../../../../generated/graphql';
 import { Button } from '../../../../components/ui/Button';
 import { Switch } from '../../../../components/ui/Switch';
 import { Input } from '../../../../components/ui/Input';
 import { validateAppConfig } from '../../utils/validators';
+import { FormField } from '../../../../core/types';
 
 interface IProps {
-  formFields: AppInfo['form_fields'];
-  onSubmit: (values: Record<string, unknown>) => void;
-  initalValues?: { exposed?: boolean; domain?: string } & { [key: string]: string };
+  formFields: FormField[];
+  onSubmit: (values: FormValues) => void;
+  initalValues?: { exposed?: boolean; domain?: string } & { [key: string]: string | boolean | undefined };
   loading?: boolean;
   exposable?: boolean | null;
 }

+ 8 - 8
packages/dashboard/src/client/modules/Apps/components/InstallModal/InstallModal.test.tsx

@@ -1,26 +1,26 @@
 import React from 'react';
 import { InstallModal } from './InstallModal';
-import { FieldTypesEnum } from '../../../../generated/graphql';
 import { fireEvent, render, screen, waitFor } from '../../../../../../tests/test-utils';
+import { AppInfo } from '../../../../core/types';
 
 describe('InstallModal', () => {
   const app = {
     name: 'My App',
     form_fields: [
-      { name: 'hostname', label: 'Hostname', type: FieldTypesEnum.Text, required: true, env_variable: 'test_hostname' },
-      { name: 'password', label: 'Password', type: FieldTypesEnum.Text, required: true, env_variable: 'test_password' },
+      { name: 'hostname', label: 'Hostname', type: 'text', required: true, env_variable: 'test_hostname' },
+      { name: 'password', label: 'Password', type: 'text', required: true, env_variable: 'test_password' },
     ],
     exposable: true,
-  };
+  } as unknown as AppInfo;
 
   it('renders with the correct title', () => {
-    render(<InstallModal app={app} isOpen onClose={jest.fn()} onSubmit={jest.fn()} />);
+    render(<InstallModal info={app} isOpen onClose={jest.fn()} onSubmit={jest.fn()} />);
 
     expect(screen.getByText(`Install ${app.name}`)).toBeInTheDocument();
   });
 
   it('renders the InstallForm with the correct props', () => {
-    render(<InstallModal app={app} isOpen onClose={jest.fn()} onSubmit={jest.fn()} />);
+    render(<InstallModal info={app} isOpen onClose={jest.fn()} onSubmit={jest.fn()} />);
 
     expect(screen.getByLabelText(app.form_fields[0]?.label || '')).toBeInTheDocument();
     expect(screen.getByLabelText(app.form_fields[1]?.label || '')).toBeInTheDocument();
@@ -28,7 +28,7 @@ describe('InstallModal', () => {
 
   it('calls onClose when the close button is clicked', () => {
     const onClose = jest.fn();
-    render(<InstallModal app={app} isOpen onClose={onClose} onSubmit={jest.fn()} />);
+    render(<InstallModal info={app} isOpen onClose={onClose} onSubmit={jest.fn()} />);
 
     fireEvent.click(screen.getByTestId('modal-close-button'));
     expect(onClose).toHaveBeenCalled();
@@ -36,7 +36,7 @@ describe('InstallModal', () => {
 
   it('calls onSubmit with the correct values when the form is submitted', async () => {
     const onSubmit = jest.fn();
-    render(<InstallModal app={app} isOpen onClose={jest.fn()} onSubmit={onSubmit} />);
+    render(<InstallModal info={app} isOpen onClose={jest.fn()} onSubmit={onSubmit} />);
 
     const hostnameInput = screen.getByLabelText(app.form_fields[0]?.label || '');
     const passwordInput = screen.getByLabelText(app.form_fields[1]?.label || '');

+ 7 - 6
packages/dashboard/src/client/modules/Apps/components/InstallModal/InstallModal.tsx

@@ -1,22 +1,23 @@
 import React from 'react';
 import { InstallForm } from '../InstallForm';
-import { AppInfo } from '../../../../generated/graphql';
 import { Modal, ModalBody, ModalHeader } from '../../../../components/ui/Modal';
+import { AppInfo } from '../../../../core/types';
+import { FormValues } from '../InstallForm/InstallForm';
 
 interface IProps {
-  app: Pick<AppInfo, 'name' | 'form_fields' | 'exposable'>;
+  info: AppInfo;
   isOpen: boolean;
   onClose: () => void;
-  onSubmit: (values: Record<string, any>) => void;
+  onSubmit: (values: FormValues) => void;
 }
 
-export const InstallModal: React.FC<IProps> = ({ app, isOpen, onClose, onSubmit }) => (
+export const InstallModal: React.FC<IProps> = ({ info, isOpen, onClose, onSubmit }) => (
   <Modal onClose={onClose} isOpen={isOpen}>
     <ModalHeader>
-      <h5 className="modal-title">Install {app.name}</h5>
+      <h5 className="modal-title">Install {info.name}</h5>
     </ModalHeader>
     <ModalBody>
-      <InstallForm onSubmit={onSubmit} formFields={app.form_fields} exposable={app.exposable} />
+      <InstallForm onSubmit={onSubmit} formFields={info.form_fields} exposable={info.exposable} />
     </ModalBody>
   </Modal>
 );

+ 5 - 5
packages/dashboard/src/client/modules/Apps/components/StopModal.tsx

@@ -1,25 +1,25 @@
 import React from 'react';
-import { AppInfo } from '../../../generated/graphql';
 import { Button } from '../../../components/ui/Button';
 import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../components/ui/Modal';
+import { AppInfo } from '../../../core/types';
 
 interface IProps {
-  app: AppInfo;
+  info: AppInfo;
   isOpen: boolean;
   onClose: () => void;
   onConfirm: () => void;
 }
 
-export const StopModal: React.FC<IProps> = ({ app, isOpen, onClose, onConfirm }) => (
+export const StopModal: React.FC<IProps> = ({ info, isOpen, onClose, onConfirm }) => (
   <Modal size="sm" onClose={onClose} isOpen={isOpen}>
     <ModalHeader>
-      <h5 className="modal-title">Stop {app.name} ?</h5>
+      <h5 className="modal-title">Stop {info.name} ?</h5>
     </ModalHeader>
     <ModalBody>
       <div className="text-muted">All data will be retained</div>
     </ModalBody>
     <ModalFooter>
-      <Button onClick={onConfirm} className="btn-danger">
+      <Button data-testid="modal-stop-button" onClick={onConfirm} className="btn-danger">
         Stop
       </Button>
     </ModalFooter>

+ 4 - 5
packages/dashboard/src/client/modules/Apps/components/UninstallModal.tsx

@@ -2,20 +2,19 @@ import { IconAlertTriangle } from '@tabler/icons';
 import React from 'react';
 import { Button } from '../../../components/ui/Button';
 import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../components/ui/Modal';
-
-import { AppInfo } from '../../../generated/graphql';
+import { AppInfo } from '../../../core/types';
 
 interface IProps {
-  app: AppInfo;
+  info: AppInfo;
   isOpen: boolean;
   onClose: () => void;
   onConfirm: () => void;
 }
 
-export const UninstallModal: React.FC<IProps> = ({ app, isOpen, onClose, onConfirm }) => (
+export const UninstallModal: React.FC<IProps> = ({ info, isOpen, onClose, onConfirm }) => (
   <Modal size="sm" type="danger" onClose={onClose} isOpen={isOpen}>
     <ModalHeader>
-      <h5 className="modal-title">Uninstall {app.name} ?</h5>
+      <h5 className="modal-title">Uninstall {info.name} ?</h5>
     </ModalHeader>
     <ModalBody className="text-center py-4">
       <IconAlertTriangle className="icon mb-2 text-danger icon-lg" />

+ 4 - 4
packages/dashboard/src/client/modules/Apps/components/UpdateModal/UpdateModal.test.tsx

@@ -8,7 +8,7 @@ describe('UpdateModal', () => {
 
   it('renders with the correct title and version number', () => {
     // Arrange
-    render(<UpdateModal app={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={jest.fn()} />);
+    render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={jest.fn()} />);
 
     // Assert
     expect(screen.getByText(`Update ${app.name} ?`)).toBeInTheDocument();
@@ -17,7 +17,7 @@ describe('UpdateModal', () => {
 
   it('should not render when isOpen is false', () => {
     // Arrange
-    render(<UpdateModal app={app} newVersion={newVersion} isOpen={false} onClose={jest.fn()} onConfirm={jest.fn()} />);
+    render(<UpdateModal info={app} newVersion={newVersion} isOpen={false} onClose={jest.fn()} onConfirm={jest.fn()} />);
     const modal = screen.queryByTestId('modal');
 
     // Assert (modal should have style display: none)
@@ -27,7 +27,7 @@ describe('UpdateModal', () => {
   it('calls onClose when the close button is clicked', () => {
     // Arrange
     const onClose = jest.fn();
-    render(<UpdateModal app={app} newVersion={newVersion} isOpen onClose={onClose} onConfirm={jest.fn()} />);
+    render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={onClose} onConfirm={jest.fn()} />);
 
     // Act
     const closeButton = screen.getByTestId('modal-close-button');
@@ -38,7 +38,7 @@ describe('UpdateModal', () => {
   it('calls onConfirm when the update button is clicked', () => {
     // Arrange
     const onConfirm = jest.fn();
-    render(<UpdateModal app={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={onConfirm} />);
+    render(<UpdateModal info={app} newVersion={newVersion} isOpen onClose={jest.fn()} onConfirm={onConfirm} />);
 
     // Act
     const updateButton = screen.getByText('Update');

+ 5 - 6
packages/dashboard/src/client/modules/Apps/components/UpdateModal/UpdateModal.tsx

@@ -1,21 +1,20 @@
 import React from 'react';
 import { Button } from '../../../../components/ui/Button';
 import { Modal, ModalBody, ModalFooter, ModalHeader } from '../../../../components/ui/Modal';
-
-import { AppInfo } from '../../../../generated/graphql';
+import { AppInfo } from '../../../../core/types';
 
 interface IProps {
   newVersion: string;
-  app: Pick<AppInfo, 'name'>;
+  info: Pick<AppInfo, 'name'>;
   isOpen: boolean;
   onClose: () => void;
   onConfirm: () => void;
 }
 
-export const UpdateModal: React.FC<IProps> = ({ app, newVersion, isOpen, onClose, onConfirm }) => (
+export const UpdateModal: React.FC<IProps> = ({ info, newVersion, isOpen, onClose, onConfirm }) => (
   <Modal size="sm" onClose={onClose} isOpen={isOpen}>
     <ModalHeader>
-      <h5 className="modal-title">Update {app.name} ?</h5>
+      <h5 className="modal-title">Update {info.name} ?</h5>
     </ModalHeader>
     <ModalBody>
       <div className="text-muted">
@@ -24,7 +23,7 @@ export const UpdateModal: React.FC<IProps> = ({ app, newVersion, isOpen, onClose
       </div>
     </ModalBody>
     <ModalFooter>
-      <Button onClick={onConfirm} className="btn-success">
+      <Button data-testid="modal-update-button" onClick={onConfirm} className="btn-success">
         Update
       </Button>
     </ModalFooter>

+ 8 - 7
packages/dashboard/src/client/modules/Apps/components/UpdateSettingsModal.tsx

@@ -1,25 +1,26 @@
 import React from 'react';
 import { InstallForm } from './InstallForm';
-import { App, AppInfo } from '../../../generated/graphql';
 import { Modal, ModalBody, ModalHeader } from '../../../components/ui/Modal';
+import { AppInfo } from '../../../core/types';
+import { FormValues } from './InstallForm/InstallForm';
 
 interface IProps {
-  app: AppInfo;
-  config: App['config'];
+  info: AppInfo;
+  config: Record<string, unknown>;
   isOpen: boolean;
   exposed?: boolean;
   domain?: string;
   onClose: () => void;
-  onSubmit: (values: Record<string, any>) => void;
+  onSubmit: (values: FormValues) => void;
 }
 
-export const UpdateSettingsModal: React.FC<IProps> = ({ app, config, isOpen, onClose, onSubmit, exposed, domain }) => (
+export const UpdateSettingsModal: React.FC<IProps> = ({ info, config, isOpen, onClose, onSubmit, exposed, domain }) => (
   <Modal onClose={onClose} isOpen={isOpen}>
     <ModalHeader>
-      <h5 className="modal-title">Update {app.name} config</h5>
+      <h5 className="modal-title">Update {info.name} config</h5>
     </ModalHeader>
     <ModalBody>
-      <InstallForm onSubmit={onSubmit} formFields={app.form_fields} exposable={app.exposable} initalValues={{ ...config, exposed, domain }} />
+      <InstallForm onSubmit={onSubmit} formFields={info.form_fields} exposable={info.exposable} initalValues={{ ...config, exposed, domain }} />
     </ModalBody>
   </Modal>
 );

+ 30 - 21
packages/dashboard/src/client/modules/Apps/utils/validators/validators.test.tsx

@@ -1,4 +1,4 @@
-import { FieldTypesEnum, FormField } from '../../../../generated/graphql';
+import { FormField } from '../../../../core/types';
 import { validateAppConfig, validateField } from './validators';
 
 describe('Test: validateField', () => {
@@ -7,7 +7,7 @@ describe('Test: validateField', () => {
       label: 'Username',
       required: true,
       env_variable: 'test',
-      type: FieldTypesEnum.Text,
+      type: 'text',
     };
     const value: string | undefined | boolean = undefined;
     const result = validateField(field, value);
@@ -17,9 +17,10 @@ describe('Test: validateField', () => {
   it('should return "field label must be less than field.max characters" if the field type is text and the value is longer than the max value', () => {
     const field: FormField = {
       label: 'Description',
-      type: FieldTypesEnum.Text,
+      type: 'text',
       max: 10,
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'This value is too long';
     const result = validateField(field, value);
@@ -29,9 +30,10 @@ describe('Test: validateField', () => {
   it('should return "field label must be at least field.min characters" if the field type is text and the value is shorter than the min value', () => {
     const field: FormField = {
       label: 'Description',
-      type: FieldTypesEnum.Text,
+      type: 'text',
       min: 20,
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'This is too short';
     const result = validateField(field, value);
@@ -42,10 +44,11 @@ describe('Test: validateField', () => {
   it('should return "field label must be between field.min and field.max characters" if the field type is password and the value is not between the min and max values', () => {
     const field: FormField = {
       label: 'Password',
-      type: FieldTypesEnum.Password,
+      type: 'password',
       min: 6,
       max: 10,
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'pass';
     const result = validateField(field, value);
@@ -55,8 +58,9 @@ describe('Test: validateField', () => {
   it('should return "field label must be a valid email address" if the field type is email and the value is not a valid email', () => {
     const field: FormField = {
       label: 'Email',
-      type: FieldTypesEnum.Email,
+      type: 'email',
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'invalid-email';
     const result = validateField(field, value);
@@ -66,8 +70,9 @@ describe('Test: validateField', () => {
   it('should return "field label must be a number" if the field type is number and the value is not a number', () => {
     const field: FormField = {
       label: 'Age',
-      type: FieldTypesEnum.Number,
+      type: 'number',
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'not a number';
     const result = validateField(field, value);
@@ -77,8 +82,9 @@ describe('Test: validateField', () => {
   it('should return "field label must be a valid domain" if the field type is fqdn and the value is not a valid domain', () => {
     const field: FormField = {
       label: 'Domain',
-      type: FieldTypesEnum.Fqdn,
+      type: 'fqdn',
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'not.a.valid.c';
     const result = validateField(field, value);
@@ -88,8 +94,9 @@ describe('Test: validateField', () => {
   it('should return "field label must be a valid IP address" if the field type is ip and the value is not a valid IP address', () => {
     const field: FormField = {
       label: 'IP Address',
-      type: FieldTypesEnum.Ip,
+      type: 'ip',
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'not a valid IP';
     const result = validateField(field, value);
@@ -99,8 +106,9 @@ describe('Test: validateField', () => {
   it('should return "field label must be a valid domain or IP address" if the field type is fqdnip and the value is not a valid domain or IP address', () => {
     const field: FormField = {
       label: 'Domain or IP',
-      type: FieldTypesEnum.Fqdnip,
+      type: 'fqdnip',
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'not a valid domain or IP';
     const result = validateField(field, value);
@@ -110,8 +118,9 @@ describe('Test: validateField', () => {
   it('should return "field label must be a valid URL" if the field type is url and the value is not a valid URL', () => {
     const field: FormField = {
       label: 'Website',
-      type: FieldTypesEnum.Url,
+      type: 'url',
       env_variable: 'test',
+      required: false,
     };
     const value: string | undefined | boolean = 'not a valid URL';
     const result = validateField(field, value);
@@ -123,7 +132,7 @@ describe('Test: validateField', () => {
       label: 'Username',
       required: false,
       env_variable: 'test',
-      type: FieldTypesEnum.Text,
+      type: 'text',
     };
     const value: string | undefined | boolean = undefined;
     const result = validateField(field, value);
@@ -135,7 +144,7 @@ describe('Test: validateField', () => {
       label: 'Username',
       required: true,
       env_variable: 'test',
-      type: FieldTypesEnum.Text,
+      type: 'text',
     };
     const value: string | undefined | boolean = true;
     const result = validateField(field, value);
@@ -155,13 +164,13 @@ describe('Test: validateAppConfig', () => {
     const fields: FormField[] = [
       {
         label: 'Username',
-        type: FieldTypesEnum.Text,
+        type: 'text',
         required: true,
         env_variable: 'username',
       },
       {
         label: 'Password',
-        type: FieldTypesEnum.Password,
+        type: 'password',
         required: true,
         min: 6,
         max: 10,
@@ -169,7 +178,7 @@ describe('Test: validateAppConfig', () => {
       },
       {
         label: 'Email',
-        type: FieldTypesEnum.Email,
+        type: 'email',
         required: true,
         env_variable: 'email',
       },
@@ -194,13 +203,13 @@ describe('Test: validateAppConfig', () => {
     const fields: FormField[] = [
       {
         label: 'Username',
-        type: FieldTypesEnum.Text,
+        type: 'text',
         required: true,
         env_variable: 'username',
       },
       {
         label: 'Password',
-        type: FieldTypesEnum.Password,
+        type: 'password',
         required: true,
         min: 6,
         max: 10,
@@ -208,7 +217,7 @@ describe('Test: validateAppConfig', () => {
       },
       {
         label: 'Email',
-        type: FieldTypesEnum.Email,
+        type: 'email',
         required: true,
         env_variable: 'email',
       },
@@ -226,7 +235,7 @@ describe('Test: validateAppConfig', () => {
     const fields: FormField[] = [
       {
         label: 'Username',
-        type: FieldTypesEnum.Text,
+        type: 'text',
         required: false,
         env_variable: 'username',
       },
@@ -245,7 +254,7 @@ describe('Test: validateAppConfig', () => {
     const fields: FormField[] = [
       {
         label: 'Username',
-        type: FieldTypesEnum.Text,
+        type: 'text',
         required: true,
         env_variable: 'username',
       },

+ 9 - 9
packages/dashboard/src/client/modules/Apps/utils/validators/validators.ts

@@ -1,5 +1,5 @@
 import validator from 'validator';
-import { FieldTypesEnum, FormField } from '../../../../generated/graphql';
+import type { FormField } from '../../../../core/types';
 
 export const validateField = (field: FormField, value: string | undefined | boolean): string | undefined => {
   if (field.required && !value) {
@@ -11,7 +11,7 @@ export const validateField = (field: FormField, value: string | undefined | bool
   }
 
   switch (field.type) {
-    case FieldTypesEnum.Text:
+    case 'text':
       if (field.max && value.length > field.max) {
         return `${field.label} must be less than ${field.max} characters`;
       }
@@ -19,37 +19,37 @@ export const validateField = (field: FormField, value: string | undefined | bool
         return `${field.label} must be at least ${field.min} characters`;
       }
       break;
-    case FieldTypesEnum.Password:
+    case 'password':
       if (!validator.isLength(value, { min: field.min || 0, max: field.max || 100 })) {
         return `${field.label} must be between ${String(field.min)} and ${String(field.max)} characters`;
       }
       break;
-    case FieldTypesEnum.Email:
+    case 'email':
       if (!validator.isEmail(value)) {
         return `${field.label} must be a valid email address`;
       }
       break;
-    case FieldTypesEnum.Number:
+    case 'number':
       if (!validator.isNumeric(value)) {
         return `${field.label} must be a number`;
       }
       break;
-    case FieldTypesEnum.Fqdn:
+    case 'fqdn':
       if (!validator.isFQDN(value)) {
         return `${field.label} must be a valid domain`;
       }
       break;
-    case FieldTypesEnum.Ip:
+    case 'ip':
       if (!validator.isIP(value)) {
         return `${field.label} must be a valid IP address`;
       }
       break;
-    case FieldTypesEnum.Fqdnip:
+    case 'fqdnip':
       if (!validator.isFQDN(value || '') && !validator.isIP(value)) {
         return `${field.label} must be a valid domain or IP address`;
       }
       break;
-    case FieldTypesEnum.Url:
+    case 'url':
       if (!validator.isURL(value)) {
         return `${field.label} must be a valid URL`;
       }