Merge branch 'release/0.7.3' into develop
This commit is contained in:
commit
a7b97ed34c
53 changed files with 602 additions and 804 deletions
|
@ -57,8 +57,6 @@ services:
|
|||
tipi-db:
|
||||
condition: service_healthy
|
||||
container_name: api
|
||||
ports:
|
||||
- 3001:3001
|
||||
volumes:
|
||||
- ${PWD}/repos:/runtipi/repos:ro
|
||||
- ${PWD}/apps:/runtipi/apps
|
||||
|
@ -103,14 +101,8 @@ services:
|
|||
depends_on:
|
||||
api:
|
||||
condition: service_started
|
||||
ports:
|
||||
- 3000:3000
|
||||
networks:
|
||||
- tipi_main_network
|
||||
environment:
|
||||
INTERNAL_IP: ${INTERNAL_IP}
|
||||
DOMAIN: ${DOMAIN}
|
||||
NGINX_PORT: ${NGINX_PORT-80}
|
||||
volumes:
|
||||
- ${PWD}/packages/dashboard/src:/dashboard/src
|
||||
# - /dashboard/node_modules
|
||||
|
|
|
@ -103,10 +103,7 @@ services:
|
|||
api:
|
||||
condition: service_started
|
||||
environment:
|
||||
INTERNAL_IP: ${INTERNAL_IP}
|
||||
NODE_ENV: production
|
||||
DOMAIN: ${DOMAIN}
|
||||
NGINX_PORT: ${NGINX_PORT-80}
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
|
||||
|
|
|
@ -104,10 +104,7 @@ services:
|
|||
api:
|
||||
condition: service_started
|
||||
environment:
|
||||
INTERNAL_IP: ${INTERNAL_IP}
|
||||
NODE_ENV: production
|
||||
DOMAIN: ${DOMAIN}
|
||||
NGINX_PORT: ${NGINX_PORT-80}
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "runtipi",
|
||||
"version": "0.7.2",
|
||||
"version": "0.7.3",
|
||||
"description": "A homeserver for everyone",
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"remark-breaks": "^3.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-mdx": "^2.1.1",
|
||||
"semver": "^7.3.7",
|
||||
"swr": "^1.3.0",
|
||||
"tslib": "^2.4.0",
|
||||
"validator": "^13.7.0",
|
||||
|
@ -48,6 +49,7 @@
|
|||
"@types/react": "18.0.8",
|
||||
"@types/react-dom": "18.0.3",
|
||||
"@types/react-slick": "^0.23.8",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/validator": "^13.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
"@typescript-eslint/parser": "^5.0.0",
|
||||
|
|
|
@ -67,6 +67,7 @@ export type AppInfo = {
|
|||
requirements?: Maybe<Scalars['JSONObject']>;
|
||||
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']>;
|
||||
|
@ -90,6 +91,12 @@ export enum AppStatusEnum {
|
|||
Updating = 'UPDATING',
|
||||
}
|
||||
|
||||
export enum AppSupportedArchitecturesEnum {
|
||||
Amd64 = 'AMD64',
|
||||
Arm = 'ARM',
|
||||
Arm64 = 'ARM64',
|
||||
}
|
||||
|
||||
export type Cpu = {
|
||||
__typename?: 'Cpu';
|
||||
load: Scalars['Float'];
|
||||
|
@ -146,34 +153,42 @@ export type Mutation = {
|
|||
updateAppConfig: App;
|
||||
};
|
||||
|
||||
|
||||
export type MutationInstallAppArgs = {
|
||||
input: AppInputType;
|
||||
};
|
||||
|
||||
|
||||
export type MutationLoginArgs = {
|
||||
input: UsernamePasswordInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRegisterArgs = {
|
||||
input: UsernamePasswordInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationStartAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationStopAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationUninstallAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationUpdateAppConfigArgs = {
|
||||
input: AppInputType;
|
||||
};
|
||||
|
@ -190,6 +205,7 @@ export type Query = {
|
|||
version: VersionResponse;
|
||||
};
|
||||
|
||||
|
||||
export type QueryGetAppArgs = {
|
||||
id: Scalars['String'];
|
||||
};
|
||||
|
@ -236,182 +252,125 @@ export type InstallAppMutationVariables = Exact<{
|
|||
input: AppInputType;
|
||||
}>;
|
||||
|
||||
export type InstallAppMutation = { __typename?: 'Mutation'; installApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
export type InstallAppMutation = { __typename?: 'Mutation', installApp: { __typename: 'App', id: string, status: AppStatusEnum } };
|
||||
|
||||
export type LoginMutationVariables = Exact<{
|
||||
input: UsernamePasswordInput;
|
||||
}>;
|
||||
|
||||
export type LoginMutation = { __typename?: 'Mutation'; login: { __typename?: 'TokenResponse'; token: string } };
|
||||
|
||||
export type LogoutMutationVariables = Exact<{ [key: string]: never }>;
|
||||
export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'TokenResponse', token: string } };
|
||||
|
||||
export type LogoutMutation = { __typename?: 'Mutation'; logout: boolean };
|
||||
export type LogoutMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type LogoutMutation = { __typename?: 'Mutation', logout: boolean };
|
||||
|
||||
export type RegisterMutationVariables = Exact<{
|
||||
input: UsernamePasswordInput;
|
||||
}>;
|
||||
|
||||
export type RegisterMutation = { __typename?: 'Mutation'; register: { __typename?: 'TokenResponse'; token: string } };
|
||||
|
||||
export type RestartMutationVariables = Exact<{ [key: string]: never }>;
|
||||
export type RegisterMutation = { __typename?: 'Mutation', register: { __typename?: 'TokenResponse', token: string } };
|
||||
|
||||
export type RestartMutation = { __typename?: 'Mutation'; restart: boolean };
|
||||
export type RestartMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type RestartMutation = { __typename?: 'Mutation', restart: boolean };
|
||||
|
||||
export type StartAppMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type StartAppMutation = { __typename?: 'Mutation'; startApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
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 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 UpdateMutationVariables = Exact<{ [key: string]: never }>;
|
||||
export type UninstallAppMutation = { __typename?: 'Mutation', uninstallApp: { __typename: 'App', id: string, status: AppStatusEnum } };
|
||||
|
||||
export type UpdateMutation = { __typename?: 'Mutation'; update: boolean };
|
||||
export type UpdateMutationVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type UpdateMutation = { __typename?: 'Mutation', update: boolean };
|
||||
|
||||
export type UpdateAppMutationVariables = Exact<{
|
||||
id: Scalars['String'];
|
||||
}>;
|
||||
|
||||
export type UpdateAppMutation = { __typename?: 'Mutation'; updateApp: { __typename: 'App'; id: string; status: AppStatusEnum } };
|
||||
|
||||
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 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;
|
||||
form_fields: Array<{
|
||||
__typename?: 'FormField';
|
||||
type: FieldTypesEnum;
|
||||
label: string;
|
||||
max?: number | null;
|
||||
min?: number | null;
|
||||
hint?: string | null;
|
||||
required?: boolean | null;
|
||||
env_variable: string;
|
||||
}>;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
|
||||
export type InstalledAppsQueryVariables = Exact<{ [key: string]: never }>;
|
||||
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, form_fields: Array<{ __typename?: 'FormField', type: FieldTypesEnum, label: string, max?: number | null, min?: number | null, hint?: string | null, required?: boolean | null, env_variable: string }> } | null } };
|
||||
|
||||
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 InstalledAppsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type ConfiguredQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type ConfiguredQuery = { __typename?: 'Query'; isConfigured: boolean };
|
||||
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 ConfiguredQueryVariables = 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 type MeQueryVariables = Exact<{ [key: string]: never }>;
|
||||
export type ConfiguredQuery = { __typename?: 'Query', isConfigured: boolean };
|
||||
|
||||
export type MeQuery = { __typename?: 'Query'; me?: { __typename?: 'User'; id: string } | null };
|
||||
export type ListAppsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type RefreshTokenQueryVariables = Exact<{ [key: string]: never }>;
|
||||
|
||||
export type RefreshTokenQuery = { __typename?: 'Query'; refreshToken?: { __typename?: 'TokenResponse'; token: string } | null };
|
||||
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 type SystemInfoQueryVariables = Exact<{ [key: string]: never }>;
|
||||
export type MeQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
export type SystemInfoQuery = {
|
||||
__typename?: 'Query';
|
||||
systemInfo?: {
|
||||
__typename?: 'SystemInfoResponse';
|
||||
cpu: { __typename?: 'Cpu'; load: number };
|
||||
disk: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
|
||||
memory: { __typename?: 'DiskMemory'; available: number; used: number; total: number };
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type VersionQueryVariables = Exact<{ [key: string]: never }>;
|
||||
export type MeQuery = { __typename?: 'Query', me?: { __typename?: 'User', id: string } | null };
|
||||
|
||||
export type RefreshTokenQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type RefreshTokenQuery = { __typename?: 'Query', refreshToken?: { __typename?: 'TokenResponse', token: string } | null };
|
||||
|
||||
export type SystemInfoQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type SystemInfoQuery = { __typename?: 'Query', systemInfo?: { __typename?: 'SystemInfoResponse', cpu: { __typename?: 'Cpu', load: number }, disk: { __typename?: 'DiskMemory', available: number, used: number, total: number }, memory: { __typename?: 'DiskMemory', available: number, used: number, total: number } } | null };
|
||||
|
||||
export type VersionQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type VersionQuery = { __typename?: 'Query', version: { __typename?: 'VersionResponse', current: string, latest?: string | null } };
|
||||
|
||||
export type VersionQuery = { __typename?: 'Query'; version: { __typename?: 'VersionResponse'; current: string; latest?: string | null } };
|
||||
|
||||
export const InstallAppDocument = gql`
|
||||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
mutation InstallApp($input: AppInputType!) {
|
||||
installApp(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type InstallAppMutationFn = Apollo.MutationFunction<InstallAppMutation, InstallAppMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -439,12 +398,12 @@ export type InstallAppMutationHookResult = ReturnType<typeof useInstallAppMutati
|
|||
export type InstallAppMutationResult = Apollo.MutationResult<InstallAppMutation>;
|
||||
export type InstallAppMutationOptions = Apollo.BaseMutationOptions<InstallAppMutation, InstallAppMutationVariables>;
|
||||
export const LoginDocument = gql`
|
||||
mutation Login($input: UsernamePasswordInput!) {
|
||||
login(input: $input) {
|
||||
token
|
||||
}
|
||||
mutation Login($input: UsernamePasswordInput!) {
|
||||
login(input: $input) {
|
||||
token
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type LoginMutationFn = Apollo.MutationFunction<LoginMutation, LoginMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -472,10 +431,10 @@ export type LoginMutationHookResult = ReturnType<typeof useLoginMutation>;
|
|||
export type LoginMutationResult = Apollo.MutationResult<LoginMutation>;
|
||||
export type LoginMutationOptions = Apollo.BaseMutationOptions<LoginMutation, LoginMutationVariables>;
|
||||
export const LogoutDocument = gql`
|
||||
mutation Logout {
|
||||
logout
|
||||
}
|
||||
`;
|
||||
mutation Logout {
|
||||
logout
|
||||
}
|
||||
`;
|
||||
export type LogoutMutationFn = Apollo.MutationFunction<LogoutMutation, LogoutMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -502,12 +461,12 @@ export type LogoutMutationHookResult = ReturnType<typeof useLogoutMutation>;
|
|||
export type LogoutMutationResult = Apollo.MutationResult<LogoutMutation>;
|
||||
export type LogoutMutationOptions = Apollo.BaseMutationOptions<LogoutMutation, LogoutMutationVariables>;
|
||||
export const RegisterDocument = gql`
|
||||
mutation Register($input: UsernamePasswordInput!) {
|
||||
register(input: $input) {
|
||||
token
|
||||
}
|
||||
mutation Register($input: UsernamePasswordInput!) {
|
||||
register(input: $input) {
|
||||
token
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type RegisterMutationFn = Apollo.MutationFunction<RegisterMutation, RegisterMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -535,10 +494,10 @@ export type RegisterMutationHookResult = ReturnType<typeof useRegisterMutation>;
|
|||
export type RegisterMutationResult = Apollo.MutationResult<RegisterMutation>;
|
||||
export type RegisterMutationOptions = Apollo.BaseMutationOptions<RegisterMutation, RegisterMutationVariables>;
|
||||
export const RestartDocument = gql`
|
||||
mutation Restart {
|
||||
restart
|
||||
}
|
||||
`;
|
||||
mutation Restart {
|
||||
restart
|
||||
}
|
||||
`;
|
||||
export type RestartMutationFn = Apollo.MutationFunction<RestartMutation, RestartMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -565,14 +524,14 @@ export type RestartMutationHookResult = ReturnType<typeof useRestartMutation>;
|
|||
export type RestartMutationResult = Apollo.MutationResult<RestartMutation>;
|
||||
export type RestartMutationOptions = Apollo.BaseMutationOptions<RestartMutation, RestartMutationVariables>;
|
||||
export const StartAppDocument = gql`
|
||||
mutation StartApp($id: String!) {
|
||||
startApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
mutation StartApp($id: String!) {
|
||||
startApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type StartAppMutationFn = Apollo.MutationFunction<StartAppMutation, StartAppMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -600,14 +559,14 @@ 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
|
||||
}
|
||||
mutation StopApp($id: String!) {
|
||||
stopApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type StopAppMutationFn = Apollo.MutationFunction<StopAppMutation, StopAppMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -635,14 +594,14 @@ 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
|
||||
}
|
||||
mutation UninstallApp($id: String!) {
|
||||
uninstallApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type UninstallAppMutationFn = Apollo.MutationFunction<UninstallAppMutation, UninstallAppMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -670,10 +629,10 @@ export type UninstallAppMutationHookResult = ReturnType<typeof useUninstallAppMu
|
|||
export type UninstallAppMutationResult = Apollo.MutationResult<UninstallAppMutation>;
|
||||
export type UninstallAppMutationOptions = Apollo.BaseMutationOptions<UninstallAppMutation, UninstallAppMutationVariables>;
|
||||
export const UpdateDocument = gql`
|
||||
mutation Update {
|
||||
update
|
||||
}
|
||||
`;
|
||||
mutation Update {
|
||||
update
|
||||
}
|
||||
`;
|
||||
export type UpdateMutationFn = Apollo.MutationFunction<UpdateMutation, UpdateMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -700,14 +659,14 @@ export type UpdateMutationHookResult = ReturnType<typeof useUpdateMutation>;
|
|||
export type UpdateMutationResult = Apollo.MutationResult<UpdateMutation>;
|
||||
export type UpdateMutationOptions = Apollo.BaseMutationOptions<UpdateMutation, UpdateMutationVariables>;
|
||||
export const UpdateAppDocument = gql`
|
||||
mutation UpdateApp($id: String!) {
|
||||
updateApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
mutation UpdateApp($id: String!) {
|
||||
updateApp(id: $id) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type UpdateAppMutationFn = Apollo.MutationFunction<UpdateAppMutation, UpdateAppMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -735,14 +694,14 @@ 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
|
||||
}
|
||||
mutation UpdateAppConfig($input: AppInputType!) {
|
||||
updateAppConfig(input: $input) {
|
||||
id
|
||||
status
|
||||
__typename
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
export type UpdateAppConfigMutationFn = Apollo.MutationFunction<UpdateAppConfigMutation, UpdateAppConfigMutationVariables>;
|
||||
|
||||
/**
|
||||
|
@ -770,47 +729,47 @@ export type UpdateAppConfigMutationHookResult = ReturnType<typeof useUpdateAppCo
|
|||
export type UpdateAppConfigMutationResult = Apollo.MutationResult<UpdateAppConfigMutation>;
|
||||
export type UpdateAppConfigMutationOptions = Apollo.BaseMutationOptions<UpdateAppConfigMutation, UpdateAppConfigMutationVariables>;
|
||||
export const GetAppDocument = gql`
|
||||
query GetApp($appId: String!) {
|
||||
getApp(id: $appId) {
|
||||
query GetApp($appId: String!) {
|
||||
getApp(id: $appId) {
|
||||
id
|
||||
status
|
||||
config
|
||||
version
|
||||
exposed
|
||||
domain
|
||||
updateInfo {
|
||||
current
|
||||
latest
|
||||
dockerVersion
|
||||
}
|
||||
info {
|
||||
id
|
||||
status
|
||||
config
|
||||
port
|
||||
name
|
||||
description
|
||||
available
|
||||
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
|
||||
form_fields {
|
||||
type
|
||||
label
|
||||
max
|
||||
min
|
||||
hint
|
||||
required
|
||||
env_variable
|
||||
}
|
||||
tipi_version
|
||||
short_desc
|
||||
author
|
||||
source
|
||||
categories
|
||||
url_suffix
|
||||
https
|
||||
exposable
|
||||
form_fields {
|
||||
type
|
||||
label
|
||||
max
|
||||
min
|
||||
hint
|
||||
required
|
||||
env_variable
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useGetAppQuery__
|
||||
|
@ -840,28 +799,28 @@ 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 {
|
||||
query InstalledApps {
|
||||
installedApps {
|
||||
id
|
||||
status
|
||||
config
|
||||
version
|
||||
updateInfo {
|
||||
current
|
||||
latest
|
||||
dockerVersion
|
||||
}
|
||||
info {
|
||||
id
|
||||
status
|
||||
config
|
||||
version
|
||||
updateInfo {
|
||||
current
|
||||
latest
|
||||
dockerVersion
|
||||
}
|
||||
info {
|
||||
id
|
||||
name
|
||||
description
|
||||
tipi_version
|
||||
short_desc
|
||||
https
|
||||
}
|
||||
name
|
||||
description
|
||||
tipi_version
|
||||
short_desc
|
||||
https
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useInstalledAppsQuery__
|
||||
|
@ -890,10 +849,10 @@ export type InstalledAppsQueryHookResult = ReturnType<typeof useInstalledAppsQue
|
|||
export type InstalledAppsLazyQueryHookResult = ReturnType<typeof useInstalledAppsLazyQuery>;
|
||||
export type InstalledAppsQueryResult = Apollo.QueryResult<InstalledAppsQuery, InstalledAppsQueryVariables>;
|
||||
export const ConfiguredDocument = gql`
|
||||
query Configured {
|
||||
isConfigured
|
||||
}
|
||||
`;
|
||||
query Configured {
|
||||
isConfigured
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useConfiguredQuery__
|
||||
|
@ -922,24 +881,24 @@ export type ConfiguredQueryHookResult = ReturnType<typeof useConfiguredQuery>;
|
|||
export type ConfiguredLazyQueryHookResult = ReturnType<typeof useConfiguredLazyQuery>;
|
||||
export type ConfiguredQueryResult = Apollo.QueryResult<ConfiguredQuery, ConfiguredQueryVariables>;
|
||||
export const ListAppsDocument = gql`
|
||||
query ListApps {
|
||||
listAppsInfo {
|
||||
apps {
|
||||
id
|
||||
available
|
||||
tipi_version
|
||||
port
|
||||
name
|
||||
version
|
||||
short_desc
|
||||
author
|
||||
categories
|
||||
https
|
||||
}
|
||||
total
|
||||
query ListApps {
|
||||
listAppsInfo {
|
||||
apps {
|
||||
id
|
||||
available
|
||||
tipi_version
|
||||
port
|
||||
name
|
||||
version
|
||||
short_desc
|
||||
author
|
||||
categories
|
||||
https
|
||||
}
|
||||
total
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useListAppsQuery__
|
||||
|
@ -968,12 +927,12 @@ export type ListAppsQueryHookResult = ReturnType<typeof useListAppsQuery>;
|
|||
export type ListAppsLazyQueryHookResult = ReturnType<typeof useListAppsLazyQuery>;
|
||||
export type ListAppsQueryResult = Apollo.QueryResult<ListAppsQuery, ListAppsQueryVariables>;
|
||||
export const MeDocument = gql`
|
||||
query Me {
|
||||
me {
|
||||
id
|
||||
}
|
||||
query Me {
|
||||
me {
|
||||
id
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useMeQuery__
|
||||
|
@ -1002,12 +961,12 @@ export type MeQueryHookResult = ReturnType<typeof useMeQuery>;
|
|||
export type MeLazyQueryHookResult = ReturnType<typeof useMeLazyQuery>;
|
||||
export type MeQueryResult = Apollo.QueryResult<MeQuery, MeQueryVariables>;
|
||||
export const RefreshTokenDocument = gql`
|
||||
query RefreshToken {
|
||||
refreshToken {
|
||||
token
|
||||
}
|
||||
query RefreshToken {
|
||||
refreshToken {
|
||||
token
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useRefreshTokenQuery__
|
||||
|
@ -1036,24 +995,24 @@ export type RefreshTokenQueryHookResult = ReturnType<typeof useRefreshTokenQuery
|
|||
export type RefreshTokenLazyQueryHookResult = ReturnType<typeof useRefreshTokenLazyQuery>;
|
||||
export type RefreshTokenQueryResult = Apollo.QueryResult<RefreshTokenQuery, RefreshTokenQueryVariables>;
|
||||
export const SystemInfoDocument = gql`
|
||||
query SystemInfo {
|
||||
systemInfo {
|
||||
cpu {
|
||||
load
|
||||
}
|
||||
disk {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
memory {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
query SystemInfo {
|
||||
systemInfo {
|
||||
cpu {
|
||||
load
|
||||
}
|
||||
disk {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
memory {
|
||||
available
|
||||
used
|
||||
total
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useSystemInfoQuery__
|
||||
|
@ -1082,13 +1041,13 @@ export type SystemInfoQueryHookResult = ReturnType<typeof useSystemInfoQuery>;
|
|||
export type SystemInfoLazyQueryHookResult = ReturnType<typeof useSystemInfoLazyQuery>;
|
||||
export type SystemInfoQueryResult = Apollo.QueryResult<SystemInfoQuery, SystemInfoQueryVariables>;
|
||||
export const VersionDocument = gql`
|
||||
query Version {
|
||||
version {
|
||||
current
|
||||
latest
|
||||
}
|
||||
query Version {
|
||||
version {
|
||||
current
|
||||
latest
|
||||
}
|
||||
`;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useVersionQuery__
|
||||
|
@ -1115,4 +1074,4 @@ export function useVersionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<Ve
|
|||
}
|
||||
export type VersionQueryHookResult = ReturnType<typeof useVersionQuery>;
|
||||
export type VersionLazyQueryHookResult = ReturnType<typeof useVersionLazyQuery>;
|
||||
export type VersionQueryResult = Apollo.QueryResult<VersionQuery, VersionQueryVariables>;
|
||||
export type VersionQueryResult = Apollo.QueryResult<VersionQuery, VersionQueryVariables>;
|
|
@ -2,19 +2,14 @@ import { useEffect, useState } from 'react';
|
|||
import { ApolloClient } from '@apollo/client';
|
||||
import { createApolloClient } from '../core/apollo/client';
|
||||
import { useSystemStore } from '../state/systemStore';
|
||||
import useSWR, { Fetcher } from 'swr';
|
||||
import { getUrl } from '../core/helpers/url-helpers';
|
||||
|
||||
interface IReturnProps {
|
||||
client?: ApolloClient<unknown>;
|
||||
isLoadingComplete?: boolean;
|
||||
}
|
||||
|
||||
const fetcher: Fetcher<{ ip: string; domain: string; port: string }, string> = (...args) => fetch(...args).then((res) => res.json());
|
||||
|
||||
export default function useCachedResources(): IReturnProps {
|
||||
const { data } = useSWR(getUrl('api/getenv'), fetcher);
|
||||
const { baseUrl, setBaseUrl, setInternalIp, setDomain } = useSystemStore();
|
||||
const { baseUrl, setBaseUrl } = useSystemStore();
|
||||
const [isLoadingComplete, setLoadingComplete] = useState(false);
|
||||
const [client, setClient] = useState<ApolloClient<unknown>>();
|
||||
|
||||
|
@ -32,23 +27,15 @@ export default function useCachedResources(): IReturnProps {
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const { ip, domain, port } = data || {};
|
||||
const hostname = window.location.hostname;
|
||||
const port = window.location.port;
|
||||
|
||||
if (ip && !baseUrl) {
|
||||
setInternalIp(ip);
|
||||
setDomain(domain);
|
||||
|
||||
if (!domain || domain === 'tipi.localhost') {
|
||||
if (port === '80') {
|
||||
setBaseUrl(`http://${ip}/api`);
|
||||
} else {
|
||||
setBaseUrl(`http://${ip}:${port}/api`);
|
||||
}
|
||||
} else {
|
||||
setBaseUrl(`https://${domain}/api`);
|
||||
}
|
||||
if (!port) {
|
||||
setBaseUrl(`http://${hostname}/api`);
|
||||
} else {
|
||||
setBaseUrl(`http://${hostname}:${port}/api`);
|
||||
}
|
||||
}, [baseUrl, setBaseUrl, setInternalIp, setDomain, data]);
|
||||
}, [setBaseUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (baseUrl) {
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { SlideFade, Flex, Divider, useDisclosure, useToast } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { FiExternalLink } from 'react-icons/fi';
|
||||
import { useSystemStore } from '../../../state/systemStore';
|
||||
import AppActions from '../components/AppActions';
|
||||
import InstallModal from '../components/InstallModal';
|
||||
import StopModal from '../components/StopModal';
|
||||
|
@ -48,8 +47,6 @@ const AppDetails: React.FC<IProps> = ({ app, info }) => {
|
|||
|
||||
const updateAvailable = Number(app?.updateInfo?.current || 0) < Number(app?.updateInfo?.latest);
|
||||
|
||||
const { internalIp } = useSystemStore();
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
if (error instanceof Error) {
|
||||
toast({
|
||||
|
@ -139,7 +136,9 @@ const AppDetails: React.FC<IProps> = ({ app, info }) => {
|
|||
const protocol = https ? 'https' : 'http';
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.open(`${protocol}://${internalIp}:${info.port}${info.url_suffix || ''}`, '_blank', 'noreferrer');
|
||||
// Current domain
|
||||
const domain = window.location.hostname;
|
||||
window.open(`${protocol}://${domain}:${info.port}${info.url_suffix || ''}`, '_blank', 'noreferrer');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
export default function getEnv(_: any, res: any) {
|
||||
const { INTERNAL_IP } = process.env;
|
||||
const { NGINX_PORT } = process.env;
|
||||
const { DOMAIN } = process.env;
|
||||
|
||||
res.status(200).json({ ip: INTERNAL_IP, domain: DOMAIN, port: NGINX_PORT });
|
||||
}
|
|
@ -3,6 +3,7 @@ import { AlertDialog, AlertDialogBody, AlertDialogContent, AlertDialogFooter, Al
|
|||
import Layout from '../components/Layout';
|
||||
import { useLogoutMutation, useRestartMutation, useUpdateMutation, useVersionQuery } from '../generated/graphql';
|
||||
import { useRef, useState } from 'react';
|
||||
import semver from 'semver';
|
||||
|
||||
const Settings: NextPage = () => {
|
||||
const toast = useToast();
|
||||
|
@ -15,7 +16,9 @@ const Settings: NextPage = () => {
|
|||
const [restart] = useRestartMutation();
|
||||
const [update] = useUpdateMutation();
|
||||
const [logout] = useLogoutMutation({ refetchQueries: ['Me'] });
|
||||
const isLatest = data?.version.latest === data?.version.current;
|
||||
|
||||
const defaultVersion = '0.0.0';
|
||||
const isLatest = semver.gte(data?.version.latest || defaultVersion, data?.version.current || defaultVersion);
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
if (error instanceof Error) {
|
||||
|
|
|
@ -8,22 +8,14 @@ export enum SystemStatus {
|
|||
|
||||
type Store = {
|
||||
baseUrl: string;
|
||||
internalIp: string;
|
||||
domain: string;
|
||||
status: SystemStatus;
|
||||
setDomain: (domain?: string) => void;
|
||||
setBaseUrl: (url: string) => void;
|
||||
setInternalIp: (ip: string) => void;
|
||||
setStatus: (status: SystemStatus) => void;
|
||||
};
|
||||
|
||||
export const useSystemStore = create<Store>((set) => ({
|
||||
baseUrl: '',
|
||||
internalIp: '',
|
||||
domain: '',
|
||||
status: SystemStatus.RUNNING,
|
||||
setDomain: (domain?: string) => set((state) => ({ ...state, domain: domain || '' })),
|
||||
setBaseUrl: (url: string) => set((state) => ({ ...state, baseUrl: url })),
|
||||
setInternalIp: (ip: string) => set((state) => ({ ...state, internalIp: ip })),
|
||||
setStatus: (status: SystemStatus) => set((state) => ({ ...state, status })),
|
||||
}));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const objectKeys = <T>(obj: T): (keyof T)[] => Object.keys(obj) as (keyof T)[];
|
||||
const objectKeys = <T extends object>(obj: T): (keyof T)[] => Object.keys(obj) as (keyof T)[];
|
||||
|
||||
function nonNullable<T>(value: T): value is NonNullable<T> {
|
||||
return value !== null && value !== undefined;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module.exports = {
|
||||
env: { node: true, jest: true },
|
||||
extends: ['airbnb-typescript', 'eslint:recommended', 'plugin:import/typescript'],
|
||||
plugins: ['@typescript-eslint', 'import', 'react'],
|
||||
extends: ['airbnb-base', 'airbnb-typescript/base', 'eslint:recommended', 'plugin:import/typescript', 'plugin:@typescript-eslint/recommended', 'prettier'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
|
@ -8,18 +8,19 @@ module.exports = {
|
|||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import', 'react'],
|
||||
rules: {
|
||||
'arrow-body-style': 0,
|
||||
'no-restricted-exports': 0,
|
||||
'max-len': [1, { code: 200 }],
|
||||
'import/extensions': ['error', 'ignorePackages', { js: 'never', jsx: 'never', ts: 'never', tsx: 'never' }],
|
||||
indent: 'off',
|
||||
'@typescript-eslint/indent': 0,
|
||||
'no-unused-vars': [1, { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/no-unused-vars': [1, { argsIgnorePattern: '^_' }],
|
||||
'max-classes-per-file': 0,
|
||||
'class-methods-use-this': 0,
|
||||
'import/prefer-default-export': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'@typescript-eslint/ban-ts-comment': 0,
|
||||
},
|
||||
globals: {
|
||||
NodeJS: true,
|
||||
},
|
||||
env: { node: true, jest: true },
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const childProcess: { execFile: typeof execFile } = jest.genMockFromModule('child_process');
|
||||
|
||||
const execFile = (_path: string, _args: string[], _thing: any, callback: Function) => {
|
||||
const execFile = (_path: string, _args: string[], _thing: unknown, callback: () => void) => {
|
||||
callback();
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import path from 'path';
|
||||
|
||||
const fs: {
|
||||
__createMockFiles: typeof createMockFiles;
|
||||
__resetAllMocks: typeof resetAllMocks;
|
||||
|
@ -20,7 +21,7 @@ const createMockFiles = (newMockFiles: Record<string, string>) => {
|
|||
mockFiles = Object.create(null);
|
||||
|
||||
// Create folder tree
|
||||
for (const file in newMockFiles) {
|
||||
Object.keys(newMockFiles).forEach((file) => {
|
||||
const dir = path.dirname(file);
|
||||
|
||||
if (!mockFiles[dir]) {
|
||||
|
@ -29,18 +30,14 @@ const createMockFiles = (newMockFiles: Record<string, string>) => {
|
|||
|
||||
mockFiles[dir].push(path.basename(file));
|
||||
mockFiles[file] = newMockFiles[file];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const readFileSync = (p: string) => {
|
||||
return mockFiles[p];
|
||||
};
|
||||
const readFileSync = (p: string) => mockFiles[p];
|
||||
|
||||
const existsSync = (p: string) => {
|
||||
return mockFiles[p] !== undefined;
|
||||
};
|
||||
const existsSync = (p: string) => mockFiles[p] !== undefined;
|
||||
|
||||
const writeFileSync = (p: string, data: any) => {
|
||||
const writeFileSync = (p: string, data: string | string[]) => {
|
||||
mockFiles[p] = data;
|
||||
};
|
||||
|
||||
|
@ -85,7 +82,7 @@ const copySync = (source: string, destination: string) => {
|
|||
|
||||
if (mockFiles[source] instanceof Array) {
|
||||
mockFiles[source].forEach((file: string) => {
|
||||
mockFiles[destination + '/' + file] = mockFiles[source + '/' + file];
|
||||
mockFiles[`${destination}/${file}`] = mockFiles[`${source}/${file}`];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -120,4 +117,5 @@ fs.createFileSync = createFileSync;
|
|||
fs.__createMockFiles = createMockFiles;
|
||||
fs.__resetAllMocks = resetAllMocks;
|
||||
|
||||
module.exports = fs;
|
||||
export default fs;
|
||||
// module.exports = fs;
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
import { faker } from '@faker-js/faker';
|
||||
|
||||
const internalIp: { v4: typeof v4Mock } = jest.genMockFromModule('internal-ip');
|
||||
|
||||
const v4Mock = () => {
|
||||
return faker.internet.ipv4();
|
||||
};
|
||||
|
||||
internalIp.v4 = v4Mock;
|
||||
|
||||
module.exports = internalIp;
|
|
@ -9,16 +9,10 @@ module.exports = {
|
|||
values.set(key, value);
|
||||
expirations.set(key, exp);
|
||||
},
|
||||
get: (key: string) => {
|
||||
return values.get(key);
|
||||
},
|
||||
get: (key: string) => values.get(key),
|
||||
quit: jest.fn(),
|
||||
del: (key: string) => {
|
||||
return values.delete(key);
|
||||
},
|
||||
ttl: (key: string) => {
|
||||
return expirations.get(key);
|
||||
},
|
||||
del: (key: string) => values.delete(key),
|
||||
ttl: (key: string) => expirations.get(key),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
import portUsed, { TcpPortUsedOptions } from 'tcp-port-used';
|
||||
|
||||
const internalIp: { check: typeof portUsed.check } = jest.genMockFromModule('tcp-port-used');
|
||||
|
||||
internalIp.check = async (_: number | TcpPortUsedOptions, __?: string | undefined) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = internalIp;
|
|
@ -37,16 +37,13 @@
|
|||
"graphql": "^15.3.0",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"http": "0.0.1-security",
|
||||
"internal-ip": "^6.0.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-cron": "^3.0.1",
|
||||
"node-port-scanner": "^3.0.1",
|
||||
"pg": "^8.7.3",
|
||||
"redis": "^4.3.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"semver": "^7.3.7",
|
||||
"tcp-port-used": "^1.0.2",
|
||||
"type-graphql": "^1.1.1",
|
||||
"typeorm": "^0.3.6",
|
||||
"uuid": "^9.0.0",
|
||||
|
@ -67,7 +64,6 @@
|
|||
"@types/node-cron": "^3.0.2",
|
||||
"@types/pg": "^8.6.5",
|
||||
"@types/semver": "^7.3.12",
|
||||
"@types/tcp-port-used": "^1.0.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@types/validator": "^13.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
|
|
|
@ -22,5 +22,5 @@ export default new DataSource({
|
|||
logging: !__prod__,
|
||||
synchronize: false,
|
||||
entities: [App, User, Update],
|
||||
migrations: [process.cwd() + '/dist/config/migrations/*.js'],
|
||||
migrations: [`${process.cwd()}/dist/config/migrations/*.js`],
|
||||
});
|
||||
|
|
|
@ -4,15 +4,13 @@ import { __prod__ } from '../constants/constants';
|
|||
import logger from './logger';
|
||||
|
||||
const ApolloLogs: PluginDefinition = {
|
||||
requestDidStart: async () => {
|
||||
return {
|
||||
requestDidStart: async () => ({
|
||||
async didEncounterErrors(errors) {
|
||||
if (!__prod__) {
|
||||
logger.error(JSON.stringify(errors.errors));
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export { ApolloLogs };
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable max-len */
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class Initial1657299198975 implements MigrationInterface {
|
||||
|
|
|
@ -105,14 +105,14 @@ class Config {
|
|||
this.config = parsed;
|
||||
}
|
||||
|
||||
public setConfig<T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile: boolean = false) {
|
||||
public setConfig<T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile = false) {
|
||||
const newConf: z.infer<typeof configSchema> = { ...this.getConfig() };
|
||||
newConf[key] = value;
|
||||
|
||||
this.config = configSchema.parse(newConf);
|
||||
|
||||
if (writeFile) {
|
||||
const currentJsonConf = readJsonFile('/runtipi/state/settings.json') || {};
|
||||
const currentJsonConf = readJsonFile<Partial<z.infer<typeof configSchema>>>('/runtipi/state/settings.json') || {};
|
||||
currentJsonConf[key] = value;
|
||||
const partialConfig = configSchema.partial();
|
||||
const parsed = partialConfig.parse(currentJsonConf);
|
||||
|
@ -122,7 +122,7 @@ class Config {
|
|||
}
|
||||
}
|
||||
|
||||
export const setConfig = <T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile: boolean = false) => {
|
||||
export const setConfig = <T extends keyof typeof configSchema.shape>(key: T, value: z.infer<typeof configSchema>[T], writeFile = false) => {
|
||||
Config.getInstance().setConfig(key, value, writeFile);
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ const WATCH_FILE = '/runtipi/state/events';
|
|||
|
||||
jest.mock('fs-extra');
|
||||
|
||||
// eslint-disable-next-line no-promise-executor-return
|
||||
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -29,7 +30,7 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
eventDispatcher.dispatchEvent(EventTypes.APP, ['--help']);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(2);
|
||||
});
|
||||
|
@ -39,12 +40,12 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
eventDispatcher.dispatchEvent(EventTypes.UPDATE, ['--help']);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
await wait(1050);
|
||||
|
||||
// @ts-ignore
|
||||
const lock = eventDispatcher.lock;
|
||||
const { lock } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(2);
|
||||
expect(lock).toBeDefined();
|
||||
|
@ -59,7 +60,7 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
await wait(1050);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(0);
|
||||
});
|
||||
|
@ -72,7 +73,7 @@ describe('EventDispatcher - dispatchEvent', () => {
|
|||
await wait(1050);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(0);
|
||||
});
|
||||
|
@ -161,7 +162,7 @@ describe('EventDispatcher - clearEvent', () => {
|
|||
eventDispatcher.clearEvent(event);
|
||||
|
||||
// @ts-ignore
|
||||
const queue = eventDispatcher.queue;
|
||||
const { queue } = eventDispatcher;
|
||||
|
||||
expect(queue.length).toBe(0);
|
||||
});
|
||||
|
@ -174,7 +175,7 @@ describe('EventDispatcher - pollQueue', () => {
|
|||
// @ts-ignore
|
||||
const id = eventDispatcher.pollQueue();
|
||||
// @ts-ignore
|
||||
const interval = eventDispatcher.interval;
|
||||
const { interval } = eventDispatcher;
|
||||
|
||||
expect(interval).toBe(123);
|
||||
expect(id).toBe(123);
|
||||
|
@ -192,7 +193,7 @@ describe('EventDispatcher - collectLockStatusAndClean', () => {
|
|||
eventDispatcher.collectLockStatusAndClean();
|
||||
|
||||
// @ts-ignore
|
||||
const lock = eventDispatcher.lock;
|
||||
const { lock } = eventDispatcher;
|
||||
|
||||
expect(lock).toBeNull();
|
||||
});
|
||||
|
|
|
@ -48,7 +48,7 @@ describe('Test: setConfig', () => {
|
|||
expect(config).toBeDefined();
|
||||
expect(config.appsRepoUrl).toBe(randomWord);
|
||||
|
||||
const settingsJson = readJsonFile('/runtipi/state/settings.json');
|
||||
const settingsJson = readJsonFile<any>('/runtipi/state/settings.json');
|
||||
|
||||
expect(settingsJson).toBeDefined();
|
||||
expect(settingsJson.appsRepoUrl).toBe(randomWord);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import cron from 'node-cron';
|
||||
import { getConfig } from '../../config/TipiConfig';
|
||||
import startJobs from '../jobs';
|
||||
import { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher';
|
||||
import { eventDispatcher, EventTypes } from '../../config/EventDispatcher';
|
||||
|
||||
jest.mock('node-cron');
|
||||
jest.mock('child_process');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import cron from 'node-cron';
|
||||
import logger from '../../config/logger/logger';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
import { getConfig } from '../config/TipiConfig';
|
||||
import { eventDispatcher, EventTypes } from '../config/EventDispatcher';
|
||||
|
||||
const startJobs = () => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { faker } from '@faker-js/faker';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { Request, Response } from 'express';
|
||||
import TipiCache from '../../../config/TipiCache';
|
||||
import { getConfig } from '../../config/TipiConfig';
|
||||
import getSessionMiddleware from '../sessionMiddleware';
|
||||
|
@ -15,9 +16,9 @@ describe('SessionMiddleware', () => {
|
|||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
} as any;
|
||||
} as Request;
|
||||
const next = jest.fn();
|
||||
const res = {} as any;
|
||||
const res = {} as Response;
|
||||
|
||||
// Act
|
||||
await getSessionMiddleware(req, res, next);
|
||||
|
@ -41,9 +42,9 @@ describe('SessionMiddleware', () => {
|
|||
headers: {
|
||||
authorization: `Bearer ${token}`,
|
||||
},
|
||||
} as any;
|
||||
} as Request;
|
||||
const next = jest.fn();
|
||||
const res = {} as any;
|
||||
const res = {} as Response;
|
||||
|
||||
// Act
|
||||
await getSessionMiddleware(req, res, next);
|
||||
|
@ -59,9 +60,9 @@ describe('SessionMiddleware', () => {
|
|||
// Arrange
|
||||
const req = {
|
||||
headers: {},
|
||||
} as any;
|
||||
} as Request;
|
||||
const next = jest.fn();
|
||||
const res = {} as any;
|
||||
const res = {} as Response;
|
||||
|
||||
// Act
|
||||
await getSessionMiddleware(req, res, next);
|
||||
|
|
|
@ -30,9 +30,7 @@ afterAll(async () => {
|
|||
await teardownConnection(TEST_SUITE);
|
||||
});
|
||||
|
||||
const createState = (apps: string[]) => {
|
||||
return JSON.stringify({ installed: apps.join(' ') });
|
||||
};
|
||||
const createState = (apps: string[]) => JSON.stringify({ installed: apps.join(' ') });
|
||||
|
||||
describe('No state/apps.json', () => {
|
||||
it('Should do nothing and create the update with status SUCCES', async () => {
|
||||
|
@ -41,7 +39,7 @@ describe('No state/apps.json', () => {
|
|||
const update = await Update.findOne({ where: { name: 'v040' } });
|
||||
|
||||
expect(update).toBeDefined();
|
||||
expect(update!.status).toBe(UpdateStatusEnum.SUCCESS);
|
||||
expect(update?.status).toBe(UpdateStatusEnum.SUCCESS);
|
||||
|
||||
const apps = await App.find();
|
||||
|
||||
|
|
|
@ -1,9 +1,21 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import { BaseEntity, DataSource, DeepPartial } from 'typeorm';
|
||||
import logger from '../../config/logger/logger';
|
||||
import App from '../../modules/apps/app.entity';
|
||||
import User from '../../modules/auth/user.entity';
|
||||
import Update from '../../modules/system/update.entity';
|
||||
|
||||
const createUser = async (user: DeepPartial<BaseEntity>): Promise<void> => {
|
||||
await User.create(user).save();
|
||||
};
|
||||
|
||||
const createApp = async (app: DeepPartial<BaseEntity>): Promise<void> => {
|
||||
await App.create(app).save();
|
||||
};
|
||||
|
||||
const createUpdate = async (update: DeepPartial<BaseEntity>): Promise<void> => {
|
||||
await Update.create(update).save();
|
||||
};
|
||||
|
||||
const recover = async (datasource: DataSource) => {
|
||||
logger.info('Recovering broken database');
|
||||
|
||||
|
@ -18,20 +30,14 @@ const recover = async (datasource: DataSource) => {
|
|||
logger.info('running migrations');
|
||||
await datasource.runMigrations();
|
||||
|
||||
// create users
|
||||
for (const user of users) {
|
||||
await User.create(user).save();
|
||||
}
|
||||
// recreate users
|
||||
await Promise.all(users.map(createUser));
|
||||
|
||||
// create apps
|
||||
for (const app of apps) {
|
||||
await App.create(app).save();
|
||||
}
|
||||
await Promise.all(apps.map(createApp));
|
||||
|
||||
// create updates
|
||||
for (const update of updates) {
|
||||
await Update.create(update).save();
|
||||
}
|
||||
await Promise.all(updates.map(createUpdate));
|
||||
|
||||
logger.info(`Users recovered ${users.length}`);
|
||||
logger.info(`Apps recovered ${apps.length}`);
|
||||
|
|
|
@ -10,6 +10,41 @@ type AppsState = { installed: string };
|
|||
|
||||
const UPDATE_NAME = 'v040';
|
||||
|
||||
const migrateApp = async (appId: string): Promise<void> => {
|
||||
const app = await App.findOne({ where: { id: appId } });
|
||||
|
||||
if (!app) {
|
||||
const envFile = readFile(`/app/storage/app-data/${appId}/app.env`).toString();
|
||||
const envVars = envFile.split('\n');
|
||||
const envVarsMap = new Map<string, string>();
|
||||
|
||||
envVars.forEach((envVar) => {
|
||||
const [key, value] = envVar.split('=');
|
||||
envVarsMap.set(key, value);
|
||||
});
|
||||
|
||||
const form: Record<string, string> = {};
|
||||
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appId}/config.json`);
|
||||
configFile?.form_fields?.forEach((field) => {
|
||||
const envVar = field.env_variable;
|
||||
const envVarValue = envVarsMap.get(envVar);
|
||||
|
||||
if (envVarValue) {
|
||||
form[field.env_variable] = envVarValue;
|
||||
}
|
||||
});
|
||||
|
||||
await App.create({ id: appId, status: AppStatusEnum.STOPPED, config: form }).save();
|
||||
} else {
|
||||
logger.info('App already migrated');
|
||||
}
|
||||
};
|
||||
|
||||
const migrateUser = async (user: { email: string; password: string }): Promise<void> => {
|
||||
await User.create({ username: user.email.trim().toLowerCase(), password: user.password }).save();
|
||||
};
|
||||
|
||||
export const updateV040 = async (): Promise<void> => {
|
||||
try {
|
||||
const update = await Update.findOne({ where: { name: UPDATE_NAME } });
|
||||
|
@ -24,36 +59,7 @@ export const updateV040 = async (): Promise<void> => {
|
|||
const state: AppsState = await readJsonFile('/runtipi/state/apps.json');
|
||||
const installed: string[] = state.installed.split(' ').filter(Boolean);
|
||||
|
||||
for (const appId of installed) {
|
||||
const app = await App.findOne({ where: { id: appId } });
|
||||
|
||||
if (!app) {
|
||||
const envFile = readFile(`/app/storage/app-data/${appId}/app.env`).toString();
|
||||
const envVars = envFile.split('\n');
|
||||
const envVarsMap = new Map<string, string>();
|
||||
|
||||
envVars.forEach((envVar) => {
|
||||
const [key, value] = envVar.split('=');
|
||||
envVarsMap.set(key, value);
|
||||
});
|
||||
|
||||
const form: Record<string, string> = {};
|
||||
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appId}/config.json`);
|
||||
configFile?.form_fields?.forEach((field) => {
|
||||
const envVar = field.env_variable;
|
||||
const envVarValue = envVarsMap.get(envVar);
|
||||
|
||||
if (envVarValue) {
|
||||
form[field.env_variable] = envVarValue;
|
||||
}
|
||||
});
|
||||
|
||||
await App.create({ id: appId, status: AppStatusEnum.STOPPED, config: form }).save();
|
||||
} else {
|
||||
logger.info('App already migrated');
|
||||
}
|
||||
}
|
||||
await Promise.all(installed.map((appId) => migrateApp(appId)));
|
||||
deleteFolder('/runtipi/state/apps.json');
|
||||
}
|
||||
|
||||
|
@ -61,9 +67,7 @@ export const updateV040 = async (): Promise<void> => {
|
|||
if (fileExists('/state/users.json')) {
|
||||
const state: { email: string; password: string }[] = await readJsonFile('/runtipi/state/users.json');
|
||||
|
||||
for (const user of state) {
|
||||
await User.create({ username: user.email.trim().toLowerCase(), password: user.password }).save();
|
||||
}
|
||||
await Promise.all(state.map((user) => migrateUser(user)));
|
||||
deleteFolder('/runtipi/state/users.json');
|
||||
}
|
||||
|
||||
|
|
2
packages/system-api/src/declarations.d.ts
vendored
2
packages/system-api/src/declarations.d.ts
vendored
|
@ -4,6 +4,6 @@ declare namespace Express {
|
|||
userId?: number;
|
||||
id?: string;
|
||||
};
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ const createApp = async (props: IProps) => {
|
|||
env_variable: 'TEST_FIELD',
|
||||
},
|
||||
],
|
||||
|
||||
name: faker.random.word(),
|
||||
description: faker.random.words(),
|
||||
tipi_version: faker.datatype.number({ min: 1, max: 10 }),
|
||||
|
@ -56,7 +55,7 @@ const createApp = async (props: IProps) => {
|
|||
};
|
||||
}
|
||||
|
||||
let MockFiles: any = {};
|
||||
const MockFiles: Record<string, string | string[]> = {};
|
||||
MockFiles['/runtipi/.env'] = 'TEST=test';
|
||||
MockFiles['/runtipi/repos/repo-id'] = '';
|
||||
MockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfo);
|
||||
|
@ -71,6 +70,7 @@ const createApp = async (props: IProps) => {
|
|||
status,
|
||||
exposed,
|
||||
domain,
|
||||
version: 1,
|
||||
}).save();
|
||||
|
||||
MockFiles[`/app/storage/app-data/${appInfo.id}`] = '';
|
||||
|
|
|
@ -4,22 +4,12 @@ import { DataSource } from 'typeorm';
|
|||
import logger from '../../../config/logger/logger';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import App from '../app.entity';
|
||||
import { checkAppRequirements, checkEnvFile, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from '../apps.helpers';
|
||||
import { checkAppRequirements, checkEnvFile, ensureAppFolder, generateEnvFile, getAppInfo, getAvailableApps, getEnvMap, getUpdateInfo } from '../apps.helpers';
|
||||
import { AppInfo } from '../apps.types';
|
||||
import { createApp } from './apps.factory';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
jest.mock('child_process');
|
||||
jest.mock('internal-ip');
|
||||
|
||||
jest.mock('tcp-port-used', () => ({
|
||||
check: (port: number) => {
|
||||
if (port === 53) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
}));
|
||||
|
||||
let db: DataSource | null = null;
|
||||
const TEST_SUITE = 'appshelpers';
|
||||
|
@ -51,15 +41,6 @@ describe('checkAppRequirements', () => {
|
|||
it('Should throw an error if app does not exist', async () => {
|
||||
await expect(checkAppRequirements('not-existing-app')).rejects.toThrow('App not-existing-app not found');
|
||||
});
|
||||
|
||||
it('Should return false if a required port is in use', async () => {
|
||||
const { appInfo, MockFiles } = await createApp({ requiredPort: 53 });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
const ivValid = await checkAppRequirements(appInfo.id);
|
||||
expect(ivValid).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getEnvMap', () => {
|
||||
|
@ -100,9 +81,13 @@ describe('checkEnvFile', () => {
|
|||
try {
|
||||
checkEnvFile(app1.id);
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('New info needed. App config needs to be updated');
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('New info needed. App config needs to be updated');
|
||||
} else {
|
||||
fail('Should throw an error');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -161,9 +146,13 @@ describe('Test: generateEnvFile', () => {
|
|||
try {
|
||||
generateEnvFile(Object.assign(appEntity1, { config: { TEST_FIELD: undefined } }));
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('Variable TEST_FIELD is required');
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('Variable TEST_FIELD is required');
|
||||
} else {
|
||||
fail('Should throw an error');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -171,9 +160,13 @@ describe('Test: generateEnvFile', () => {
|
|||
try {
|
||||
generateEnvFile(Object.assign(appEntity1, { id: 'not-existing-app' }));
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('App not-existing-app not found');
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('App not-existing-app not found');
|
||||
} else {
|
||||
fail('Should throw an error');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -329,9 +322,13 @@ describe('Test: getAppInfo', () => {
|
|||
try {
|
||||
await getAppInfo(appInfo.id, appEntity.status);
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
expect(e.message).toBe(`Error loading app: ${appInfo.id}`);
|
||||
expect(log).toBeCalledWith(`Error loading app: ${appInfo.id}`);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
expect(e.message).toBe(`Error loading app: ${appInfo.id}`);
|
||||
expect(log).toBeCalledWith(`Error loading app: ${appInfo.id}`);
|
||||
} else {
|
||||
expect(true).toBe(false);
|
||||
}
|
||||
}
|
||||
|
||||
spy.mockRestore();
|
||||
|
@ -355,15 +352,89 @@ describe('getUpdateInfo', () => {
|
|||
});
|
||||
|
||||
it('Should return update info', async () => {
|
||||
const updateInfo = await getUpdateInfo(app1.id);
|
||||
const updateInfo = await getUpdateInfo(app1.id, 1);
|
||||
|
||||
expect(updateInfo?.latest).toBe(app1.tipi_version);
|
||||
expect(updateInfo?.current).toBe(1);
|
||||
});
|
||||
|
||||
it('Should return null if app is not installed', async () => {
|
||||
const updateInfo = await getUpdateInfo(faker.random.word());
|
||||
const updateInfo = await getUpdateInfo(faker.random.word(), 1);
|
||||
|
||||
expect(updateInfo).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: ensureAppFolder', () => {
|
||||
beforeEach(() => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: ['test.yml'],
|
||||
};
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
});
|
||||
|
||||
it('should copy the folder from repo', () => {
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('should not copy the folder if it already exists', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: ['test.yml'],
|
||||
'/runtipi/apps/test': ['docker-compose.yml'],
|
||||
'/runtipi/apps/test/docker-compose.yml': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['docker-compose.yml']);
|
||||
});
|
||||
|
||||
it('Should overwrite the folder if clean up is true', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: ['test.yml'],
|
||||
'/runtipi/apps/test': ['docker-compose.yml'],
|
||||
'/runtipi/apps/test/docker-compose.yml': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test', true);
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('Should delete folder if it exists but has no docker-compose.yml file', () => {
|
||||
// Arrange
|
||||
const randomFileName = `${faker.random.word()}.yml`;
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/repo-id/apps/test`]: [randomFileName],
|
||||
'/runtipi/apps/test': ['test.yml'],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual([randomFileName]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import fs from 'fs-extra';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { gcall } from '../../../test/gcall';
|
||||
import App from '../app.entity';
|
||||
import { getAppQuery, InstalledAppsQuery, listAppInfosQuery } from '../../../test/queries';
|
||||
|
@ -9,13 +10,10 @@ import { AppInfo, AppStatusEnum, ListAppsResonse } from '../apps.types';
|
|||
import { createUser } from '../../auth/__tests__/user.factory';
|
||||
import User from '../../auth/user.entity';
|
||||
import { installAppMutation, startAppMutation, stopAppMutation, uninstallAppMutation, updateAppConfigMutation, updateAppMutation } from '../../../test/mutations';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import EventDispatcher from '../../../core/config/EventDispatcher';
|
||||
|
||||
jest.mock('fs');
|
||||
jest.mock('child_process');
|
||||
jest.mock('internal-ip');
|
||||
jest.mock('tcp-port-used');
|
||||
|
||||
type TApp = App & {
|
||||
info: AppInfo;
|
||||
|
@ -220,23 +218,6 @@ describe('InstallApp', () => {
|
|||
expect(errors?.[0].message).toBe(`Variable ${app1.form_fields?.[0].env_variable} is required`);
|
||||
expect(data?.installApp).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should throw an error if the requirements are not met', async () => {
|
||||
const { appInfo, MockFiles } = await createApp({ requiredPort: 400 });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
const user = await createUser();
|
||||
|
||||
const { data, errors } = await gcall<{ installApp: TApp }>({
|
||||
source: installAppMutation,
|
||||
userId: user.id,
|
||||
variableValues: { input: { id: appInfo.id, form: { TEST_FIELD: 'hello' }, exposed: false, domain: '' } },
|
||||
});
|
||||
|
||||
expect(errors?.[0].message).toBe(`App ${appInfo.id} requirements not met`);
|
||||
expect(data?.installApp).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('StartApp', () => {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import AppsService from '../apps.service';
|
||||
import fs from 'fs-extra';
|
||||
import { DataSource } from 'typeorm';
|
||||
import AppsService from '../apps.service';
|
||||
import { AppInfo, AppStatusEnum, AppSupportedArchitecturesEnum } from '../apps.types';
|
||||
import App from '../app.entity';
|
||||
import { createApp } from './apps.factory';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { getEnvMap } from '../apps.helpers';
|
||||
import EventDispatcher, { eventDispatcher, EventTypes } from '../../../core/config/EventDispatcher';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
|
@ -56,9 +56,9 @@ describe('Install app', () => {
|
|||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
|
||||
expect(app).toBeDefined();
|
||||
expect(app!.id).toBe(app1.id);
|
||||
expect(app!.config).toStrictEqual({ TEST_FIELD: 'test' });
|
||||
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
||||
expect(app?.id).toBe(app1.id);
|
||||
expect(app?.config).toStrictEqual({ TEST_FIELD: 'test' });
|
||||
expect(app?.status).toBe(AppStatusEnum.RUNNING);
|
||||
});
|
||||
|
||||
it('Should start app if already installed', async () => {
|
||||
|
@ -147,7 +147,7 @@ describe('Install app', () => {
|
|||
const app2 = await createApp({ exposable: true });
|
||||
const app3 = await createApp({ exposable: true });
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(Object.assign({}, app2.MockFiles, app3.MockFiles));
|
||||
fs.__createMockFiles({ ...app2.MockFiles, ...app3.MockFiles });
|
||||
|
||||
await AppsService.installApp(app2.appInfo.id, { TEST_FIELD: 'test' }, true, 'test.com');
|
||||
|
||||
|
@ -203,8 +203,8 @@ describe('Uninstall app', () => {
|
|||
|
||||
// Assert
|
||||
expect(app).toBeDefined();
|
||||
expect(app!.id).toBe(app1.id);
|
||||
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
||||
expect(app?.id).toBe(app1.id);
|
||||
expect(app?.status).toBe(AppStatusEnum.RUNNING);
|
||||
});
|
||||
|
||||
it('Should correctly remove app from database', async () => {
|
||||
|
@ -244,7 +244,7 @@ describe('Uninstall app', () => {
|
|||
// Act & Assert
|
||||
await expect(AppsService.uninstallApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to uninstall\nstdout: test`);
|
||||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
||||
expect(app?.status).toBe(AppStatusEnum.STOPPED);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -300,7 +300,7 @@ describe('Start app', () => {
|
|||
// Act & Assert
|
||||
await expect(AppsService.startApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to start\nstdout: test`);
|
||||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
||||
expect(app?.status).toBe(AppStatusEnum.STOPPED);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -333,7 +333,7 @@ describe('Stop app', () => {
|
|||
// Act & Assert
|
||||
await expect(AppsService.stopApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to stop\nstdout: test`);
|
||||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
expect(app!.status).toBe(AppStatusEnum.RUNNING);
|
||||
expect(app?.status).toBe(AppStatusEnum.RUNNING);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -378,17 +378,13 @@ describe('Update app config', () => {
|
|||
expect(envMap.get('RANDOM_FIELD')).toBe('test');
|
||||
});
|
||||
|
||||
it('Should throw if app is exposed and domain is not provided', () => {
|
||||
return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true)).rejects.toThrowError('Domain is required');
|
||||
});
|
||||
it('Should throw if app is exposed and domain is not provided', () => expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true)).rejects.toThrowError('Domain is required'));
|
||||
|
||||
it('Should throw if app is exposed and domain is not valid', () => {
|
||||
return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test')).rejects.toThrowError('Domain test is not valid');
|
||||
});
|
||||
it('Should throw if app is exposed and domain is not valid', () =>
|
||||
expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test')).rejects.toThrowError('Domain test is not valid'));
|
||||
|
||||
it('Should throw if app is exposed and config does not allow it', () => {
|
||||
return expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test.com')).rejects.toThrowError(`App ${app1.id} is not exposable`);
|
||||
});
|
||||
it('Should throw if app is exposed and config does not allow it', () =>
|
||||
expect(AppsService.updateAppConfig(app1.id, { TEST_FIELD: 'test' }, true, 'test.com')).rejects.toThrowError(`App ${app1.id} is not exposable`));
|
||||
|
||||
it('Should throw if app is exposed and domain is already used', async () => {
|
||||
const app2 = await createApp({ exposable: true, installed: true });
|
||||
|
@ -592,6 +588,6 @@ describe('Update app', () => {
|
|||
|
||||
await expect(AppsService.updateApp(app1.id)).rejects.toThrow(`App ${app1.id} failed to update\nstdout: error`);
|
||||
const app = await App.findOne({ where: { id: app1.id } });
|
||||
expect(app!.status).toBe(AppStatusEnum.STOPPED);
|
||||
expect(app?.status).toBe(AppStatusEnum.STOPPED);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -70,7 +70,7 @@ class App extends BaseEntity {
|
|||
|
||||
@Field(() => UpdateInfo, { nullable: true })
|
||||
updateInfo(): Promise<UpdateInfo | null> {
|
||||
return getUpdateInfo(this.id);
|
||||
return getUpdateInfo(this.id, this.version);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
packages/system-api/src/modules/apps/app.types.ts
Normal file
6
packages/system-api/src/modules/apps/app.types.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface AppEntityType {
|
||||
id: string;
|
||||
config: Record<string, string>;
|
||||
exposed: boolean;
|
||||
domain?: string;
|
||||
}
|
|
@ -1,36 +1,23 @@
|
|||
import portUsed from 'tcp-port-used';
|
||||
import { fileExists, getSeed, readdirSync, readFile, readJsonFile, writeFile } from '../fs/fs.helpers';
|
||||
import InternalIp from 'internal-ip';
|
||||
import crypto from 'crypto';
|
||||
import fs from 'fs-extra';
|
||||
import { deleteFolder, fileExists, getSeed, readdirSync, readFile, readJsonFile, writeFile } from '../fs/fs.helpers';
|
||||
import { AppInfo, AppStatusEnum } from './apps.types';
|
||||
import logger from '../../config/logger/logger';
|
||||
import App from './app.entity';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
import fs from 'fs-extra';
|
||||
import { AppEntityType } from './app.types';
|
||||
|
||||
export const checkAppRequirements = async (appName: string) => {
|
||||
let valid = true;
|
||||
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}/config.json`);
|
||||
|
||||
if (!configFile) {
|
||||
throw new Error(`App ${appName} not found`);
|
||||
}
|
||||
|
||||
if (configFile?.requirements?.ports) {
|
||||
for (const port of configFile.requirements.ports) {
|
||||
const ip = await InternalIp.v4();
|
||||
const used = await portUsed.check(port, ip);
|
||||
|
||||
if (used) valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (configFile?.supported_architectures && !configFile.supported_architectures.includes(getConfig().architecture)) {
|
||||
throw new Error(`App ${appName} is not supported on this architecture`);
|
||||
}
|
||||
|
||||
return valid;
|
||||
return true;
|
||||
};
|
||||
|
||||
export const getEnvMap = (appName: string): Map<string, string> => {
|
||||
|
@ -66,7 +53,7 @@ const getEntropy = (name: string, length: number) => {
|
|||
return hash.digest('hex').substring(0, length);
|
||||
};
|
||||
|
||||
export const generateEnvFile = (app: App) => {
|
||||
export const generateEnvFile = (app: AppEntityType) => {
|
||||
const configFile: AppInfo | null = readJsonFile(`/runtipi/apps/${app.id}/config.json`);
|
||||
|
||||
if (!configFile) {
|
||||
|
@ -120,9 +107,9 @@ export const getAvailableApps = async (): Promise<string[]> => {
|
|||
|
||||
appsDir.forEach((app) => {
|
||||
if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`)) {
|
||||
const configFile: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`);
|
||||
const configFile = readJsonFile<AppInfo>(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`);
|
||||
|
||||
if (configFile.available) {
|
||||
if (configFile?.available) {
|
||||
apps.push(app);
|
||||
}
|
||||
}
|
||||
|
@ -137,14 +124,22 @@ export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null =
|
|||
const installed = typeof status !== 'undefined' && status !== AppStatusEnum.MISSING;
|
||||
|
||||
if (installed && fileExists(`/runtipi/apps/${id}/config.json`)) {
|
||||
const configFile: AppInfo = readJsonFile(`/runtipi/apps/${id}/config.json`);
|
||||
configFile.description = readFile(`/runtipi/apps/${id}/metadata/description.md`).toString();
|
||||
return configFile;
|
||||
} else if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`)) {
|
||||
const configFile: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
configFile.description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/metadata/description.md`);
|
||||
const configFile = readJsonFile<AppInfo>(`/runtipi/apps/${id}/config.json`);
|
||||
|
||||
if (configFile.available) {
|
||||
if (configFile) {
|
||||
configFile.description = readFile(`/runtipi/apps/${id}/metadata/description.md`).toString();
|
||||
}
|
||||
|
||||
return configFile;
|
||||
}
|
||||
if (fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`)) {
|
||||
const configFile = readJsonFile<AppInfo>(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
|
||||
if (configFile) {
|
||||
configFile.description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/metadata/description.md`);
|
||||
}
|
||||
|
||||
if (configFile?.available) {
|
||||
return configFile;
|
||||
}
|
||||
}
|
||||
|
@ -156,20 +151,36 @@ export const getAppInfo = (id: string, status?: AppStatusEnum): AppInfo | null =
|
|||
}
|
||||
};
|
||||
|
||||
export const getUpdateInfo = async (id: string) => {
|
||||
const app = await App.findOne({ where: { id } });
|
||||
|
||||
export const getUpdateInfo = async (id: string, version: number) => {
|
||||
const doesFileExist = fileExists(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}`);
|
||||
|
||||
if (!app || !doesFileExist) {
|
||||
if (!doesFileExist) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const repoConfig: AppInfo = readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
const repoConfig = readJsonFile<AppInfo>(`/runtipi/repos/${getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
|
||||
if (!repoConfig?.tipi_version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
current: app.version,
|
||||
latest: repoConfig.tipi_version,
|
||||
dockerVersion: repoConfig.version,
|
||||
current: version || 0,
|
||||
latest: repoConfig?.tipi_version,
|
||||
dockerVersion: repoConfig?.version,
|
||||
};
|
||||
};
|
||||
|
||||
export const ensureAppFolder = (appName: string, cleanup = false) => {
|
||||
if (cleanup && fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
|
||||
if (!fileExists(`/runtipi/apps/${appName}/docker-compose.yml`)) {
|
||||
if (fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
// Copy from apps repo
|
||||
fs.copySync(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}`, `/runtipi/apps/${appName}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import validator from 'validator';
|
||||
import { createFolder, ensureAppFolder, readFile, readJsonFile } from '../fs/fs.helpers';
|
||||
import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps } from './apps.helpers';
|
||||
import { Not } from 'typeorm';
|
||||
import { createFolder, readFile, readJsonFile } from '../fs/fs.helpers';
|
||||
import { checkAppRequirements, checkEnvFile, generateEnvFile, getAvailableApps, ensureAppFolder } from './apps.helpers';
|
||||
import { AppInfo, AppStatusEnum, ListAppsResonse } from './apps.types';
|
||||
import App from './app.entity';
|
||||
import logger from '../../config/logger/logger';
|
||||
import { Not } from 'typeorm';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
import { eventDispatcher, EventTypes } from '../../core/config/EventDispatcher';
|
||||
|
||||
|
@ -18,9 +18,7 @@ const filterApp = (app: AppInfo): boolean => {
|
|||
return app.supported_architectures.includes(arch);
|
||||
};
|
||||
|
||||
const filterApps = (apps: AppInfo[]): AppInfo[] => {
|
||||
return apps.sort(sortApps).filter(filterApp);
|
||||
};
|
||||
const filterApps = (apps: AppInfo[]): AppInfo[] => apps.sort(sortApps).filter(filterApp);
|
||||
|
||||
/**
|
||||
* Start all apps which had the status RUNNING in the database
|
||||
|
@ -157,11 +155,7 @@ const installApp = async (id: string, form: Record<string, string>, exposed?: bo
|
|||
const listApps = async (): Promise<ListAppsResonse> => {
|
||||
const folders: string[] = await getAvailableApps();
|
||||
|
||||
const apps: AppInfo[] = folders
|
||||
.map((app) => {
|
||||
return readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`);
|
||||
})
|
||||
.filter(Boolean);
|
||||
const apps: AppInfo[] = folders.map((app) => readJsonFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app}/config.json`)).filter(Boolean);
|
||||
|
||||
const filteredApps = filterApps(apps).map((app) => {
|
||||
const description = readFile(`/runtipi/repos/${getConfig().appsRepoId}/apps/${app.id}/metadata/description.md`);
|
||||
|
@ -254,7 +248,7 @@ const stopApp = async (id: string): Promise<App> => {
|
|||
* @returns - the app entity
|
||||
*/
|
||||
const uninstallApp = async (id: string): Promise<App> => {
|
||||
let app = await App.findOne({ where: { id } });
|
||||
const app = await App.findOne({ where: { id } });
|
||||
|
||||
if (!app) {
|
||||
throw new Error(`App ${id} not found`);
|
||||
|
|
|
@ -7,7 +7,7 @@ import { setupConnection, teardownConnection } from '../../../test/connection';
|
|||
import { gcall } from '../../../test/gcall';
|
||||
import { loginMutation, registerMutation } from '../../../test/mutations';
|
||||
import { isConfiguredQuery, MeQuery, refreshTokenQuery } from '../../../test/queries';
|
||||
import User from '../../auth/user.entity';
|
||||
import User from '../user.entity';
|
||||
import { TokenResponse } from '../auth.types';
|
||||
import { createUser } from './user.factory';
|
||||
|
||||
|
@ -214,7 +214,7 @@ describe('Test: refreshToken', () => {
|
|||
const { data } = await gcall<{ refreshToken: TokenResponse }>({
|
||||
source: refreshTokenQuery,
|
||||
userId: user1.id,
|
||||
session: session,
|
||||
session,
|
||||
});
|
||||
const decoded = jwt.verify(data?.refreshToken?.token || '', getConfig().jwtSecret) as jwt.JwtPayload;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import * as argon2 from 'argon2';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { DataSource } from 'typeorm';
|
||||
import AuthService from '../auth.service';
|
||||
import { createUser } from './user.factory';
|
||||
import User from '../user.entity';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { setupConnection, teardownConnection } from '../../../test/connection';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
import TipiCache from '../../../config/TipiCache';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import User from '../user.entity';
|
||||
import * as argon2 from 'argon2';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import User from '../user.entity';
|
||||
|
||||
const createUser = async (email?: string) => {
|
||||
const hash = await argon2.hash('password');
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Field, InputType, ObjectType } from 'type-graphql';
|
||||
import User from './user.entity';
|
||||
|
||||
@InputType()
|
||||
class UsernamePasswordInput {
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import { readJsonFile, readFile, readdirSync, fileExists, writeFile, createFolder, deleteFolder, getSeed, ensureAppFolder } from '../fs.helpers';
|
||||
import fs from 'fs-extra';
|
||||
import { getConfig } from '../../../core/config/TipiConfig';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { readJsonFile, readFile, readdirSync, fileExists, writeFile, createFolder, deleteFolder, getSeed } from '../fs.helpers';
|
||||
|
||||
jest.mock('fs-extra');
|
||||
|
||||
|
@ -15,7 +13,7 @@ describe('Test: readJsonFile', () => {
|
|||
// Arrange
|
||||
const rawFile = '{"test": "test"}';
|
||||
const mockFiles = {
|
||||
['/runtipi/test-file.json']: rawFile,
|
||||
'/runtipi/test-file.json': rawFile,
|
||||
};
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
@ -52,7 +50,7 @@ describe('Test: readFile', () => {
|
|||
it('should return the file', () => {
|
||||
const rawFile = 'test';
|
||||
const mockFiles = {
|
||||
['/runtipi/test-file.txt']: rawFile,
|
||||
'/runtipi/test-file.txt': rawFile,
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -69,7 +67,7 @@ describe('Test: readFile', () => {
|
|||
describe('Test: readdirSync', () => {
|
||||
it('should return the files', () => {
|
||||
const mockFiles = {
|
||||
['/runtipi/test/test-file.txt']: 'test',
|
||||
'/runtipi/test/test-file.txt': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -86,7 +84,7 @@ describe('Test: readdirSync', () => {
|
|||
describe('Test: fileExists', () => {
|
||||
it('should return true if the file exists', () => {
|
||||
const mockFiles = {
|
||||
['/runtipi/test-file.txt']: 'test',
|
||||
'/runtipi/test-file.txt': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -133,7 +131,7 @@ describe('Test: deleteFolder', () => {
|
|||
describe('Test: getSeed', () => {
|
||||
it('should return the seed', () => {
|
||||
const mockFiles = {
|
||||
['/runtipi/state/seed']: 'test',
|
||||
'/runtipi/state/seed': 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
|
@ -142,77 +140,3 @@ describe('Test: getSeed', () => {
|
|||
expect(getSeed()).toEqual('test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: ensureAppFolder', () => {
|
||||
beforeEach(() => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'],
|
||||
};
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
});
|
||||
|
||||
it('should copy the folder from repo', () => {
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('should not copy the folder if it already exists', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'],
|
||||
['/runtipi/apps/test']: ['docker-compose.yml'],
|
||||
['/runtipi/apps/test/docker-compose.yml']: 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['docker-compose.yml']);
|
||||
});
|
||||
|
||||
it('Should overwrite the folder if clean up is true', () => {
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: ['test.yml'],
|
||||
['/runtipi/apps/test']: ['docker-compose.yml'],
|
||||
['/runtipi/apps/test/docker-compose.yml']: 'test',
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test', true);
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual(['test.yml']);
|
||||
});
|
||||
|
||||
it('Should delete folder if it exists but has no docker-compose.yml file', () => {
|
||||
// Arrange
|
||||
const randomFileName = `${faker.random.word()}.yml`;
|
||||
const mockFiles = {
|
||||
[`/runtipi/repos/${getConfig().appsRepoId}/apps/test`]: [randomFileName],
|
||||
['/runtipi/apps/test']: ['test.yml'],
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
fs.__createMockFiles(mockFiles);
|
||||
|
||||
// Act
|
||||
ensureAppFolder('test');
|
||||
|
||||
// Assert
|
||||
const files = fs.readdirSync('/runtipi/apps/test');
|
||||
expect(files).toEqual([randomFileName]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import fs from 'fs-extra';
|
||||
import { getConfig } from '../../core/config/TipiConfig';
|
||||
|
||||
export const readJsonFile = (path: string): any => {
|
||||
export const readJsonFile = <T>(path: string): T | null => {
|
||||
try {
|
||||
const rawFile = fs.readFileSync(path).toString();
|
||||
|
||||
|
@ -23,7 +22,7 @@ export const readdirSync = (path: string): string[] => fs.readdirSync(path);
|
|||
|
||||
export const fileExists = (path: string): boolean => fs.existsSync(path);
|
||||
|
||||
export const writeFile = (path: string, data: any) => fs.writeFileSync(path, data);
|
||||
export const writeFile = (path: string, data: string) => fs.writeFileSync(path, data);
|
||||
|
||||
export const createFolder = (path: string) => {
|
||||
if (!fileExists(path)) {
|
||||
|
@ -36,17 +35,3 @@ export const getSeed = () => {
|
|||
const seed = readFile('/runtipi/state/seed');
|
||||
return seed.toString();
|
||||
};
|
||||
|
||||
export const ensureAppFolder = (appName: string, cleanup = false) => {
|
||||
if (cleanup && fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
|
||||
if (!fileExists(`/runtipi/apps/${appName}/docker-compose.yml`)) {
|
||||
if (fileExists(`/runtipi/apps/${appName}`)) {
|
||||
deleteFolder(`/runtipi/apps/${appName}`);
|
||||
}
|
||||
// Copy from apps repo
|
||||
fs.copySync(`/runtipi/repos/${getConfig().appsRepoId}/apps/${appName}`, `/runtipi/apps/${appName}`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import axios from 'axios';
|
||||
import SystemService from '../system.service';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import SystemService from '../system.service';
|
||||
import TipiCache from '../../../config/TipiCache';
|
||||
import { setConfig } from '../../../core/config/TipiConfig';
|
||||
import logger from '../../../config/logger/logger';
|
||||
|
@ -21,9 +21,13 @@ describe('Test: systemInfo', () => {
|
|||
it('Should throw if system-info.json does not exist', () => {
|
||||
try {
|
||||
SystemService.systemInfo();
|
||||
} catch (e: any) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('Error parsing system info');
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
expect(e).toBeDefined();
|
||||
expect(e.message).toBe('Error parsing system info');
|
||||
} else {
|
||||
fail('Should throw an error');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ const getVersion = async (): Promise<{ current: string; latest?: string }> => {
|
|||
version = data.name.replace('v', '');
|
||||
}
|
||||
|
||||
await TipiCache.set('latestVersion', version?.replace('v', '') || '');
|
||||
await TipiCache.set('latestVersion', version?.replace('v', '') || '', 60 * 60);
|
||||
|
||||
return { current: getConfig().version, latest: version?.replace('v', '') };
|
||||
} catch (e) {
|
||||
|
|
|
@ -2,9 +2,11 @@ import 'reflect-metadata';
|
|||
import express from 'express';
|
||||
import { ApolloServerPluginLandingPageGraphQLPlayground as Playground } from 'apollo-server-core';
|
||||
import { ApolloServer } from 'apollo-server-express';
|
||||
import { createServer } from 'http';
|
||||
import { ZodError } from 'zod';
|
||||
import cors, { CorsOptions } from 'cors';
|
||||
import { createSchema } from './schema';
|
||||
import { ApolloLogs } from './config/logger/apollo.logger';
|
||||
import { createServer } from 'http';
|
||||
import logger from './config/logger/logger';
|
||||
import getSessionMiddleware from './core/middlewares/sessionMiddleware';
|
||||
import { MyContext } from './types';
|
||||
|
@ -15,10 +17,8 @@ import { runUpdates } from './core/updates/run';
|
|||
import recover from './core/updates/recover-migrations';
|
||||
import startJobs from './core/jobs/jobs';
|
||||
import { applyJsonConfig, getConfig, setConfig } from './core/config/TipiConfig';
|
||||
import { ZodError } from 'zod';
|
||||
import systemController from './modules/system/system.controller';
|
||||
import { eventDispatcher, EventTypes } from './core/config/EventDispatcher';
|
||||
import cors from 'cors';
|
||||
|
||||
const applyCustomConfig = () => {
|
||||
try {
|
||||
|
@ -33,6 +33,13 @@ const applyCustomConfig = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const corsOptions: CorsOptions = {
|
||||
credentials: false,
|
||||
origin: (_, callback) => {
|
||||
callback(null, true);
|
||||
},
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
eventDispatcher.clear();
|
||||
|
@ -41,8 +48,8 @@ const main = async () => {
|
|||
const app = express();
|
||||
const port = 3001;
|
||||
|
||||
app.use(cors(corsOptions));
|
||||
app.use(express.static(`${getConfig().rootFolder}/repos/${getConfig().appsRepoId}`));
|
||||
app.use(cors());
|
||||
app.use('/status', systemController.status);
|
||||
app.use(getSessionMiddleware);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { DataSource } from 'typeorm';
|
||||
import pg from 'pg';
|
||||
import App from '../modules/apps/app.entity';
|
||||
import User from '../modules/auth/user.entity';
|
||||
import pg from 'pg';
|
||||
import Update from '../modules/system/update.entity';
|
||||
|
||||
const HOST = 'localhost';
|
||||
|
|
|
@ -5,7 +5,7 @@ import { createSchema } from '../schema';
|
|||
interface Options {
|
||||
source: string;
|
||||
variableValues?: Maybe<{
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
userId?: number;
|
||||
session?: string;
|
||||
|
@ -13,7 +13,7 @@ interface Options {
|
|||
|
||||
let schema: GraphQLSchema | null = null;
|
||||
|
||||
export const gcall = async <T>({ source, variableValues, userId, session }: Options): Promise<ExecutionResult<T, { [key: string]: any }>> => {
|
||||
export const gcall = async <T>({ source, variableValues, userId, session }: Options): Promise<ExecutionResult<T, { [key: string]: unknown }>> => {
|
||||
if (!schema) {
|
||||
schema = await createSchema();
|
||||
}
|
||||
|
@ -23,5 +23,5 @@ export const gcall = async <T>({ source, variableValues, userId, session }: Opti
|
|||
source,
|
||||
variableValues,
|
||||
contextValue: { req: { session: { userId, id: session } } },
|
||||
}) as any;
|
||||
}) as unknown as ExecutionResult<T, { [key: string]: unknown }>;
|
||||
};
|
||||
|
|
117
pnpm-lock.yaml
117
pnpm-lock.yaml
|
@ -35,6 +35,7 @@ importers:
|
|||
'@types/react': 18.0.8
|
||||
'@types/react-dom': 18.0.3
|
||||
'@types/react-slick': ^0.23.8
|
||||
'@types/semver': ^7.3.12
|
||||
'@types/validator': ^13.7.2
|
||||
'@typescript-eslint/eslint-plugin': ^5.18.0
|
||||
'@typescript-eslint/parser': ^5.0.0
|
||||
|
@ -60,6 +61,7 @@ importers:
|
|||
remark-breaks: ^3.0.2
|
||||
remark-gfm: ^3.0.1
|
||||
remark-mdx: ^2.1.1
|
||||
semver: ^7.3.7
|
||||
swr: ^1.3.0
|
||||
tailwindcss: ^3.0.23
|
||||
ts-jest: ^28.0.2
|
||||
|
@ -88,6 +90,7 @@ importers:
|
|||
remark-breaks: 3.0.2
|
||||
remark-gfm: 3.0.1
|
||||
remark-mdx: 2.1.1
|
||||
semver: 7.3.7
|
||||
swr: 1.3.0_react@18.1.0
|
||||
tslib: 2.4.0
|
||||
validator: 13.7.0
|
||||
|
@ -103,6 +106,7 @@ importers:
|
|||
'@types/react': 18.0.8
|
||||
'@types/react-dom': 18.0.3
|
||||
'@types/react-slick': 0.23.8
|
||||
'@types/semver': 7.3.12
|
||||
'@types/validator': 13.7.2
|
||||
'@typescript-eslint/eslint-plugin': 5.22.0_oztpoyrbzkyaikrhdkppp3gagu
|
||||
'@typescript-eslint/parser': 5.22.0_uhoeudlwl7kc47h4kncsfowede
|
||||
|
@ -131,7 +135,6 @@ importers:
|
|||
'@types/node-cron': ^3.0.2
|
||||
'@types/pg': ^8.6.5
|
||||
'@types/semver': ^7.3.12
|
||||
'@types/tcp-port-used': ^1.0.1
|
||||
'@types/uuid': ^8.3.4
|
||||
'@types/validator': ^13.7.2
|
||||
'@typescript-eslint/eslint-plugin': ^5.18.0
|
||||
|
@ -155,12 +158,10 @@ importers:
|
|||
graphql-import-node: ^0.0.5
|
||||
graphql-type-json: ^0.3.2
|
||||
http: 0.0.1-security
|
||||
internal-ip: ^6.0.0
|
||||
jest: ^28.1.0
|
||||
jsonwebtoken: ^8.5.1
|
||||
node-cache: ^5.1.2
|
||||
node-cron: ^3.0.1
|
||||
node-port-scanner: ^3.0.1
|
||||
nodemon: ^2.0.15
|
||||
pg: ^8.7.3
|
||||
prettier: 2.6.2
|
||||
|
@ -168,7 +169,6 @@ importers:
|
|||
reflect-metadata: ^0.1.13
|
||||
rimraf: ^3.0.2
|
||||
semver: ^7.3.7
|
||||
tcp-port-used: ^1.0.2
|
||||
ts-jest: ^28.0.2
|
||||
ts-node: ^10.8.2
|
||||
type-graphql: ^1.1.1
|
||||
|
@ -191,16 +191,13 @@ importers:
|
|||
graphql: 15.8.0
|
||||
graphql-type-json: 0.3.2_graphql@15.8.0
|
||||
http: 0.0.1-security
|
||||
internal-ip: 6.2.0
|
||||
jsonwebtoken: 8.5.1
|
||||
node-cache: 5.1.2
|
||||
node-cron: 3.0.1
|
||||
node-port-scanner: 3.0.1
|
||||
pg: 8.7.3
|
||||
redis: 4.3.1
|
||||
reflect-metadata: 0.1.13
|
||||
semver: 7.3.7
|
||||
tcp-port-used: 1.0.2
|
||||
type-graphql: 1.1.1_v2revtygxcm7xrdg2oz3ssohfu
|
||||
typeorm: 0.3.6_rymjtjxvmmxrsowl5wrmwxcyqa
|
||||
uuid: 9.0.0
|
||||
|
@ -220,7 +217,6 @@ importers:
|
|||
'@types/node-cron': 3.0.2
|
||||
'@types/pg': 8.6.5
|
||||
'@types/semver': 7.3.12
|
||||
'@types/tcp-port-used': 1.0.1
|
||||
'@types/uuid': 8.3.4
|
||||
'@types/validator': 13.7.2
|
||||
'@typescript-eslint/eslint-plugin': 5.22.0_tal4xlmvnofklupd3hwjtzfb4q
|
||||
|
@ -3961,10 +3957,6 @@ packages:
|
|||
resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
|
||||
dev: true
|
||||
|
||||
/@types/tcp-port-used/1.0.1:
|
||||
resolution: {integrity: sha512-6pwWTx8oUtWvsiZUCrhrK/53MzKVLnuNSSaZILPy3uMes9QnTrLMar9BDlJArbMOjDcjb3QXFk6Rz8qmmuySZw==}
|
||||
dev: true
|
||||
|
||||
/@types/unist/2.0.6:
|
||||
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
|
||||
dev: false
|
||||
|
@ -5628,6 +5620,7 @@ packages:
|
|||
path-key: 3.1.1
|
||||
shebang-command: 2.0.0
|
||||
which: 2.0.2
|
||||
dev: true
|
||||
|
||||
/cross-undici-fetch/0.4.7:
|
||||
resolution: {integrity: sha512-e5KZdjHigxFECfw1B7cjmXLm3yT8eiffSJYUSyIWxy6c+f/MGiJsV1NHegZvG23ZgQ0o8rNaZxbtu5NdF5FmwQ==}
|
||||
|
@ -5742,18 +5735,6 @@ packages:
|
|||
supports-color: 5.5.0
|
||||
dev: true
|
||||
|
||||
/debug/4.3.1:
|
||||
resolution: {integrity: sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: false
|
||||
|
||||
/debug/4.3.4:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
|
@ -5802,19 +5783,13 @@ packages:
|
|||
|
||||
/deep-is/0.1.4:
|
||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||
dev: true
|
||||
|
||||
/deepmerge/4.2.2:
|
||||
resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/default-gateway/6.0.3:
|
||||
resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==}
|
||||
engines: {node: '>= 10'}
|
||||
dependencies:
|
||||
execa: 5.1.1
|
||||
dev: false
|
||||
|
||||
/defaults/1.0.3:
|
||||
resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==}
|
||||
dependencies:
|
||||
|
@ -6668,6 +6643,7 @@ packages:
|
|||
onetime: 5.1.2
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
dev: true
|
||||
|
||||
/exit/0.1.2:
|
||||
resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
|
||||
|
@ -7131,6 +7107,7 @@ packages:
|
|||
/get-stream/6.0.1:
|
||||
resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/get-symbol-description/1.0.0:
|
||||
resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==}
|
||||
|
@ -7527,6 +7504,7 @@ packages:
|
|||
/human-signals/2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
dev: true
|
||||
|
||||
/husky/8.0.1:
|
||||
resolution: {integrity: sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==}
|
||||
|
@ -7641,16 +7619,6 @@ packages:
|
|||
wrap-ansi: 7.0.0
|
||||
dev: true
|
||||
|
||||
/internal-ip/6.2.0:
|
||||
resolution: {integrity: sha512-D8WGsR6yDt8uq7vDMu7mjcR+yRMm3dW8yufyChmszWRjcSHuxLBkR3GdS2HZAjodsaGuCvXeEJpueisXJULghg==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
default-gateway: 6.0.3
|
||||
ipaddr.js: 1.9.1
|
||||
is-ip: 3.1.0
|
||||
p-event: 4.2.0
|
||||
dev: false
|
||||
|
||||
/internal-slot/1.0.3:
|
||||
resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
@ -7665,11 +7633,6 @@ packages:
|
|||
dependencies:
|
||||
loose-envify: 1.4.0
|
||||
|
||||
/ip-regex/4.3.0:
|
||||
resolution: {integrity: sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==}
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/ipaddr.js/1.9.1:
|
||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
@ -7805,13 +7768,6 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/is-ip/3.1.0:
|
||||
resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
ip-regex: 4.3.0
|
||||
dev: false
|
||||
|
||||
/is-lower-case/2.0.2:
|
||||
resolution: {integrity: sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==}
|
||||
dependencies:
|
||||
|
@ -7944,10 +7900,6 @@ packages:
|
|||
tslib: 2.4.0
|
||||
dev: true
|
||||
|
||||
/is-url/1.2.4:
|
||||
resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==}
|
||||
dev: false
|
||||
|
||||
/is-utf8/0.2.1:
|
||||
resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==}
|
||||
dev: true
|
||||
|
@ -7967,17 +7919,9 @@ packages:
|
|||
resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==}
|
||||
dev: true
|
||||
|
||||
/is2/2.0.7:
|
||||
resolution: {integrity: sha512-4vBQoURAXC6hnLFxD4VW7uc04XiwTTl/8ydYJxKvPwkWQrSjInkuM5VZVg6BGr1/natq69zDuvO9lGpLClJqvA==}
|
||||
engines: {node: '>=v0.10.0'}
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
ip-regex: 4.3.0
|
||||
is-url: 1.2.4
|
||||
dev: false
|
||||
|
||||
/isexe/2.0.0:
|
||||
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
|
||||
dev: true
|
||||
|
||||
/isomorphic-fetch/3.0.0:
|
||||
resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==}
|
||||
|
@ -9203,6 +9147,7 @@ packages:
|
|||
|
||||
/merge-stream/2.0.0:
|
||||
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
||||
dev: true
|
||||
|
||||
/merge/2.1.1:
|
||||
resolution: {integrity: sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==}
|
||||
|
@ -9594,6 +9539,7 @@ packages:
|
|||
/mimic-fn/2.1.0:
|
||||
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/mimic-response/1.0.1:
|
||||
resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
|
||||
|
@ -9778,10 +9724,6 @@ packages:
|
|||
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
||||
dev: true
|
||||
|
||||
/node-port-scanner/3.0.1:
|
||||
resolution: {integrity: sha512-TuFGEWfye+1atB74v0Vm6myEjpq0L5Jo3UaOG9xgtYHxnFZN0fF9CnwCxp7ENWDerGbI1UXAgdRMIPz8TM73Hg==}
|
||||
dev: false
|
||||
|
||||
/node-releases/2.0.4:
|
||||
resolution: {integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==}
|
||||
dev: true
|
||||
|
@ -9865,6 +9807,7 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
path-key: 3.1.1
|
||||
dev: true
|
||||
|
||||
/npmlog/5.0.1:
|
||||
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
|
||||
|
@ -9975,6 +9918,7 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
mimic-fn: 2.1.0
|
||||
dev: true
|
||||
|
||||
/optimism/0.16.1:
|
||||
resolution: {integrity: sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==}
|
||||
|
@ -10020,18 +9964,6 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/p-event/4.2.0:
|
||||
resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
p-timeout: 3.2.0
|
||||
dev: false
|
||||
|
||||
/p-finally/1.0.0:
|
||||
resolution: {integrity: sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/p-limit/1.3.0:
|
||||
resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -10079,13 +10011,6 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/p-timeout/3.2.0:
|
||||
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
p-finally: 1.0.0
|
||||
dev: false
|
||||
|
||||
/p-try/1.0.0:
|
||||
resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -10209,6 +10134,7 @@ packages:
|
|||
/path-key/3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/path-parse/1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
|
@ -11150,10 +11076,12 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
dev: true
|
||||
|
||||
/shebang-regex/3.0.0:
|
||||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/side-channel/1.0.4:
|
||||
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
|
||||
|
@ -11412,6 +11340,7 @@ packages:
|
|||
/strip-final-newline/2.0.0:
|
||||
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/strip-indent/3.0.0:
|
||||
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
|
||||
|
@ -11576,15 +11505,6 @@ packages:
|
|||
yallist: 4.0.0
|
||||
dev: false
|
||||
|
||||
/tcp-port-used/1.0.2:
|
||||
resolution: {integrity: sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==}
|
||||
dependencies:
|
||||
debug: 4.3.1
|
||||
is2: 2.0.7
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/terminal-link/2.1.1:
|
||||
resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -12438,6 +12358,7 @@ packages:
|
|||
hasBin: true
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
dev: true
|
||||
|
||||
/wide-align/1.1.5:
|
||||
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
|
||||
|
|
|
@ -14,7 +14,7 @@ source "${BASH_SOURCE%/*}/common.sh"
|
|||
ROOT_FOLDER="${PWD}"
|
||||
STATE_FOLDER="${ROOT_FOLDER}/state"
|
||||
SED_ROOT_FOLDER="$(echo "$ROOT_FOLDER" | sed 's/\//\\\//g')"
|
||||
NGINX_PORT=80
|
||||
NGINX_PORT=3000
|
||||
NGINX_PORT_SSL=443
|
||||
DOMAIN=tipi.localhost
|
||||
DNS_IP="9.9.9.9" # Default to Quad9 DNS
|
||||
|
|
Loading…
Reference in a new issue