refactor: removal and replace usage of old graphql generated types
This commit is contained in:
parent
783c80714b
commit
d72526ab8a
32 changed files with 191 additions and 947 deletions
39
packages/dashboard/server.ts
Normal file
39
packages/dashboard/server.ts
Normal file
|
@ -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}`);
|
||||
});
|
|
@ -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 (
|
||||
|
|
|
@ -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'>;
|
||||
|
||||
|
|
|
@ -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>;
|
|
@ -1,7 +0,0 @@
|
|||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
mutation StartApp($id: String!) {
|
||||
startApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
mutation StopApp($id: String!) {
|
||||
stopApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
mutation UninstallApp($id: String!) {
|
||||
uninstallApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
mutation UpdateApp($id: String!) {
|
||||
updateApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
mutation UpdateAppConfig($input: AppInputType!) {
|
||||
updateAppConfig(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
query InstalledApps {
|
||||
installedApps {
|
||||
id
|
||||
status
|
||||
config
|
||||
version
|
||||
updateInfo {
|
||||
current
|
||||
latest
|
||||
dockerVersion
|
||||
}
|
||||
info {
|
||||
id
|
||||
name
|
||||
description
|
||||
tipi_version
|
||||
short_desc
|
||||
https
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
))}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 || '');
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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`;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue