fetch latest version tag from docker hub (#11362)
closes #11352 --------- Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
@ -1437,6 +1437,7 @@ export type Query = {
|
|||||||
plans: Array<BillingPlanOutput>;
|
plans: Array<BillingPlanOutput>;
|
||||||
search: Array<SearchRecord>;
|
search: Array<SearchRecord>;
|
||||||
validatePasswordResetToken: ValidatePasswordResetToken;
|
validatePasswordResetToken: ValidatePasswordResetToken;
|
||||||
|
versionInfo: VersionInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -2176,6 +2177,12 @@ export type ValidatePasswordResetToken = {
|
|||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type VersionInfo = {
|
||||||
|
__typename?: 'VersionInfo';
|
||||||
|
currentVersion?: Maybe<Scalars['String']>;
|
||||||
|
latestVersion: Scalars['String'];
|
||||||
|
};
|
||||||
|
|
||||||
export type WorkerQueueMetrics = {
|
export type WorkerQueueMetrics = {
|
||||||
__typename?: 'WorkerQueueMetrics';
|
__typename?: 'WorkerQueueMetrics';
|
||||||
active: Scalars['Float'];
|
active: Scalars['Float'];
|
||||||
@ -2606,6 +2613,11 @@ export type GetEnvironmentVariablesGroupedQueryVariables = Exact<{ [key: string]
|
|||||||
|
|
||||||
export type GetEnvironmentVariablesGroupedQuery = { __typename?: 'Query', getEnvironmentVariablesGrouped: { __typename?: 'EnvironmentVariablesOutput', groups: Array<{ __typename?: 'EnvironmentVariablesGroupData', name: EnvironmentVariablesGroup, description: string, isHiddenOnLoad: boolean, variables: Array<{ __typename?: 'EnvironmentVariable', name: string, description: string, value: string, sensitive: boolean }> }> } };
|
export type GetEnvironmentVariablesGroupedQuery = { __typename?: 'Query', getEnvironmentVariablesGrouped: { __typename?: 'EnvironmentVariablesOutput', groups: Array<{ __typename?: 'EnvironmentVariablesGroupData', name: EnvironmentVariablesGroup, description: string, isHiddenOnLoad: boolean, variables: Array<{ __typename?: 'EnvironmentVariable', name: string, description: string, value: string, sensitive: boolean }> }> } };
|
||||||
|
|
||||||
|
export type GetVersionInfoQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
|
export type GetVersionInfoQuery = { __typename?: 'Query', versionInfo: { __typename?: 'VersionInfo', currentVersion?: string | null, latestVersion: string } };
|
||||||
|
|
||||||
export type GetIndicatorHealthStatusQueryVariables = Exact<{
|
export type GetIndicatorHealthStatusQueryVariables = Exact<{
|
||||||
indicatorId: HealthIndicatorId;
|
indicatorId: HealthIndicatorId;
|
||||||
}>;
|
}>;
|
||||||
@ -4555,6 +4567,41 @@ export function useGetEnvironmentVariablesGroupedLazyQuery(baseOptions?: Apollo.
|
|||||||
export type GetEnvironmentVariablesGroupedQueryHookResult = ReturnType<typeof useGetEnvironmentVariablesGroupedQuery>;
|
export type GetEnvironmentVariablesGroupedQueryHookResult = ReturnType<typeof useGetEnvironmentVariablesGroupedQuery>;
|
||||||
export type GetEnvironmentVariablesGroupedLazyQueryHookResult = ReturnType<typeof useGetEnvironmentVariablesGroupedLazyQuery>;
|
export type GetEnvironmentVariablesGroupedLazyQueryHookResult = ReturnType<typeof useGetEnvironmentVariablesGroupedLazyQuery>;
|
||||||
export type GetEnvironmentVariablesGroupedQueryResult = Apollo.QueryResult<GetEnvironmentVariablesGroupedQuery, GetEnvironmentVariablesGroupedQueryVariables>;
|
export type GetEnvironmentVariablesGroupedQueryResult = Apollo.QueryResult<GetEnvironmentVariablesGroupedQuery, GetEnvironmentVariablesGroupedQueryVariables>;
|
||||||
|
export const GetVersionInfoDocument = gql`
|
||||||
|
query GetVersionInfo {
|
||||||
|
versionInfo {
|
||||||
|
currentVersion
|
||||||
|
latestVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useGetVersionInfoQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useGetVersionInfoQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useGetVersionInfoQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||||
|
* you can use to render your UI.
|
||||||
|
*
|
||||||
|
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const { data, loading, error } = useGetVersionInfoQuery({
|
||||||
|
* variables: {
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useGetVersionInfoQuery(baseOptions?: Apollo.QueryHookOptions<GetVersionInfoQuery, GetVersionInfoQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<GetVersionInfoQuery, GetVersionInfoQueryVariables>(GetVersionInfoDocument, options);
|
||||||
|
}
|
||||||
|
export function useGetVersionInfoLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetVersionInfoQuery, GetVersionInfoQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<GetVersionInfoQuery, GetVersionInfoQueryVariables>(GetVersionInfoDocument, options);
|
||||||
|
}
|
||||||
|
export type GetVersionInfoQueryHookResult = ReturnType<typeof useGetVersionInfoQuery>;
|
||||||
|
export type GetVersionInfoLazyQueryHookResult = ReturnType<typeof useGetVersionInfoLazyQuery>;
|
||||||
|
export type GetVersionInfoQueryResult = Apollo.QueryResult<GetVersionInfoQuery, GetVersionInfoQueryVariables>;
|
||||||
export const GetIndicatorHealthStatusDocument = gql`
|
export const GetIndicatorHealthStatusDocument = gql`
|
||||||
query GetIndicatorHealthStatus($indicatorId: HealthIndicatorId!) {
|
query GetIndicatorHealthStatus($indicatorId: HealthIndicatorId!) {
|
||||||
getIndicatorHealthStatus(indicatorId: $indicatorId) {
|
getIndicatorHealthStatus(indicatorId: $indicatorId) {
|
||||||
|
|||||||
@ -1,72 +1,34 @@
|
|||||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||||
import { checkTwentyVersionExists } from '@/settings/admin-panel/utils/checkTwentyVersionExists';
|
import { SettingsAdminVersionDisplay } from '@/settings/admin-panel/components/SettingsAdminVersionDisplay';
|
||||||
import { fetchLatestTwentyRelease } from '@/settings/admin-panel/utils/fetchLatestTwentyRelease';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import packageJson from '../../../../../package.json';
|
|
||||||
import { GITHUB_LINK } from 'twenty-ui/navigation';
|
|
||||||
import { IconCircleDot, IconStatusChange } from 'twenty-ui/display';
|
import { IconCircleDot, IconStatusChange } from 'twenty-ui/display';
|
||||||
|
import { useGetVersionInfoQuery } from '~/generated/graphql';
|
||||||
const StyledActionLink = styled.a`
|
|
||||||
align-items: center;
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
|
||||||
display: flex;
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
:hover {
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledSpan = styled.span`
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const SettingsAdminVersionContainer = () => {
|
export const SettingsAdminVersionContainer = () => {
|
||||||
const [latestVersion, setLatestVersion] = useState<string | null>(null);
|
const { data, loading } = useGetVersionInfoQuery();
|
||||||
const [currentVersionExists, setCurrentVersionExists] = useState(false);
|
const { currentVersion, latestVersion } = data?.versionInfo ?? {};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchLatestTwentyRelease().then(setLatestVersion);
|
|
||||||
checkTwentyVersionExists(packageJson.version).then(setCurrentVersionExists);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const versionItems = [
|
const versionItems = [
|
||||||
{
|
{
|
||||||
Icon: IconCircleDot,
|
Icon: IconCircleDot,
|
||||||
label: t`Current version`,
|
label: t`Current version`,
|
||||||
value: currentVersionExists ? (
|
value: (
|
||||||
<StyledActionLink
|
<SettingsAdminVersionDisplay
|
||||||
href={`${GITHUB_LINK}/releases/tag/v${packageJson.version}`}
|
version={currentVersion}
|
||||||
target="_blank"
|
loading={loading}
|
||||||
rel="noreferrer"
|
noVersionMessage={t`Unknown`}
|
||||||
>
|
/>
|
||||||
{packageJson.version}
|
|
||||||
</StyledActionLink>
|
|
||||||
) : (
|
|
||||||
<StyledSpan>{packageJson.version}</StyledSpan>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Icon: IconStatusChange,
|
Icon: IconStatusChange,
|
||||||
label: t`Latest version`,
|
label: t`Latest version`,
|
||||||
value: latestVersion ? (
|
value: (
|
||||||
<StyledActionLink
|
<SettingsAdminVersionDisplay
|
||||||
href={`${GITHUB_LINK}/releases/tag/v${latestVersion}`}
|
version={latestVersion}
|
||||||
target="_blank"
|
loading={loading}
|
||||||
rel="noreferrer"
|
noVersionMessage={t`No latest version found`}
|
||||||
>
|
/>
|
||||||
{latestVersion}
|
|
||||||
</StyledActionLink>
|
|
||||||
) : (
|
|
||||||
<StyledSpan>{latestVersion ?? 'Loading...'}</StyledSpan>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
|
||||||
|
type SettingsAdminVersionDisplayProps = {
|
||||||
|
version: string | undefined | null;
|
||||||
|
loading: boolean;
|
||||||
|
noVersionMessage: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const StyledActionLink = styled.a`
|
||||||
|
align-items: center;
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
display: flex;
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledSpan = styled.span`
|
||||||
|
color: ${({ theme }) => theme.font.color.primary};
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SettingsAdminVersionDisplay = ({
|
||||||
|
version,
|
||||||
|
loading,
|
||||||
|
noVersionMessage,
|
||||||
|
}: SettingsAdminVersionDisplayProps) => {
|
||||||
|
if (loading) {
|
||||||
|
return <StyledSpan>{t`Loading...`}</StyledSpan>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!version) {
|
||||||
|
return <StyledSpan>{noVersionMessage}</StyledSpan>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledActionLink
|
||||||
|
href={`https://hub.docker.com/r/twentycrm/twenty/tags?name=${version}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
{version}
|
||||||
|
</StyledActionLink>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { gql } from '@apollo/client';
|
||||||
|
|
||||||
|
export const GET_VERSION_INFO = gql`
|
||||||
|
query GetVersionInfo {
|
||||||
|
versionInfo {
|
||||||
|
currentVersion
|
||||||
|
latestVersion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@ -1,12 +0,0 @@
|
|||||||
export const checkTwentyVersionExists = async (
|
|
||||||
version: string,
|
|
||||||
): Promise<boolean> => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
`https://api.github.com/repos/twentyhq/twenty/releases/tags/v${version}`,
|
|
||||||
);
|
|
||||||
return response.status === 200;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
export const fetchLatestTwentyRelease = async (): Promise<string> => {
|
|
||||||
try {
|
|
||||||
const response = await fetch(
|
|
||||||
'https://api.github.com/repos/twentyhq/twenty/releases/latest',
|
|
||||||
);
|
|
||||||
const data = await response.json();
|
|
||||||
return data.tag_name.replace('v', '');
|
|
||||||
} catch (error) {
|
|
||||||
return 'Could not fetch latest release';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
import { AdminPanelService } from 'src/engine/core-modules/admin-panel/admin-panel.service';
|
||||||
import {
|
import {
|
||||||
AuthException,
|
AuthException,
|
||||||
@ -276,4 +278,102 @@ describe('AdminPanelService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getVersionInfo', () => {
|
||||||
|
const mockEnvironmentGet = jest.fn();
|
||||||
|
const mockAxiosGet = jest.fn();
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEnvironmentGet.mockReset();
|
||||||
|
mockAxiosGet.mockReset();
|
||||||
|
jest.spyOn(axios, 'get').mockImplementation(mockAxiosGet);
|
||||||
|
service['environmentService'].get = mockEnvironmentGet;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return current and latest version when everything works', async () => {
|
||||||
|
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||||
|
mockAxiosGet.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{ name: '2.0.0' },
|
||||||
|
{ name: '1.5.0' },
|
||||||
|
{ name: '1.0.0' },
|
||||||
|
{ name: 'latest' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.getVersionInfo();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
currentVersion: '1.0.0',
|
||||||
|
latestVersion: '2.0.0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle undefined APP_VERSION', async () => {
|
||||||
|
mockEnvironmentGet.mockReturnValue(undefined);
|
||||||
|
mockAxiosGet.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [{ name: '2.0.0' }, { name: 'latest' }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.getVersionInfo();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
currentVersion: undefined,
|
||||||
|
latestVersion: '2.0.0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle Docker Hub API error', async () => {
|
||||||
|
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||||
|
mockAxiosGet.mockRejectedValue(new Error('API Error'));
|
||||||
|
|
||||||
|
const result = await service.getVersionInfo();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
currentVersion: '1.0.0',
|
||||||
|
latestVersion: 'latest',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle empty Docker Hub tags', async () => {
|
||||||
|
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||||
|
mockAxiosGet.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.getVersionInfo();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
currentVersion: '1.0.0',
|
||||||
|
latestVersion: 'latest',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle invalid semver tags', async () => {
|
||||||
|
mockEnvironmentGet.mockReturnValue('1.0.0');
|
||||||
|
mockAxiosGet.mockResolvedValue({
|
||||||
|
data: {
|
||||||
|
results: [
|
||||||
|
{ name: '2.0.0' },
|
||||||
|
{ name: 'invalid-version' },
|
||||||
|
{ name: 'latest' },
|
||||||
|
{ name: '1.0.0' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.getVersionInfo();
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
currentVersion: '1.0.0',
|
||||||
|
latestVersion: '2.0.0',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { SystemHealth } from 'src/engine/core-modules/admin-panel/dtos/system-he
|
|||||||
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
|
import { UpdateWorkspaceFeatureFlagInput } from 'src/engine/core-modules/admin-panel/dtos/update-workspace-feature-flag.input';
|
||||||
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||||
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
|
import { UserLookupInput } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.input';
|
||||||
|
import { VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||||
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
|
import { QueueMetricsTimeRange } from 'src/engine/core-modules/admin-panel/enums/queue-metrics-time-range.enum';
|
||||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||||
import { FeatureFlagException } from 'src/engine/core-modules/feature-flag/feature-flag.exception';
|
import { FeatureFlagException } from 'src/engine/core-modules/feature-flag/feature-flag.exception';
|
||||||
@ -114,4 +115,10 @@ export class AdminPanelResolver {
|
|||||||
timeRange,
|
timeRange,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(WorkspaceAuthGuard, UserAuthGuard, AdminPanelGuard)
|
||||||
|
@Query(() => VersionInfo)
|
||||||
|
async versionInfo(): Promise<VersionInfo> {
|
||||||
|
return this.adminService.getVersionInfo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import semver from 'semver';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
import { EnvironmentVariable } from 'src/engine/core-modules/admin-panel/dtos/environment-variable.dto';
|
import { EnvironmentVariable } from 'src/engine/core-modules/admin-panel/dtos/environment-variable.dto';
|
||||||
import { EnvironmentVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/environment-variables-group.dto';
|
import { EnvironmentVariablesGroupData } from 'src/engine/core-modules/admin-panel/dtos/environment-variables-group.dto';
|
||||||
import { EnvironmentVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/environment-variables.output';
|
import { EnvironmentVariablesOutput } from 'src/engine/core-modules/admin-panel/dtos/environment-variables.output';
|
||||||
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
import { UserLookup } from 'src/engine/core-modules/admin-panel/dtos/user-lookup.entity';
|
||||||
|
import { VersionInfo } from 'src/engine/core-modules/admin-panel/dtos/version-info.dto';
|
||||||
import {
|
import {
|
||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
@ -163,4 +166,30 @@ export class AdminPanelService {
|
|||||||
|
|
||||||
return { groups };
|
return { groups };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getVersionInfo(): Promise<VersionInfo> {
|
||||||
|
const currentVersion = this.environmentService.get('APP_VERSION');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
'https://hub.docker.com/v2/repositories/twentycrm/twenty/tags?page_size=100',
|
||||||
|
);
|
||||||
|
|
||||||
|
const versions = response.data.results
|
||||||
|
.filter((tag) => tag && tag.name !== 'latest')
|
||||||
|
.map((tag) => semver.coerce(tag.name)?.version)
|
||||||
|
.filter((version) => version !== undefined);
|
||||||
|
|
||||||
|
if (versions.length === 0) {
|
||||||
|
return { currentVersion, latestVersion: 'latest' };
|
||||||
|
}
|
||||||
|
|
||||||
|
versions.sort((a, b) => semver.compare(b, a));
|
||||||
|
const latestVersion = versions[0];
|
||||||
|
|
||||||
|
return { currentVersion, latestVersion };
|
||||||
|
} catch (error) {
|
||||||
|
return { currentVersion, latestVersion: 'latest' };
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { Field, ObjectType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class VersionInfo {
|
||||||
|
@Field(() => String, { nullable: true })
|
||||||
|
currentVersion?: string;
|
||||||
|
|
||||||
|
@Field(() => String)
|
||||||
|
latestVersion: string;
|
||||||
|
}
|
||||||
@ -11,4 +11,5 @@ export { FIELD_FOR_TOTAL_COUNT_AGGREGATE_OPERATION } from './FieldForTotalCountA
|
|||||||
export { PermissionsOnAllObjectRecords } from './PermissionsOnAllObjectRecords';
|
export { PermissionsOnAllObjectRecords } from './PermissionsOnAllObjectRecords';
|
||||||
export { STANDARD_OBJECT_RECORDS_UNDER_OBJECT_RECORDS_PERMISSIONS } from './StandardObjectRecordsUnderObjectRecordsPermissions';
|
export { STANDARD_OBJECT_RECORDS_UNDER_OBJECT_RECORDS_PERMISSIONS } from './StandardObjectRecordsUnderObjectRecordsPermissions';
|
||||||
export { TWENTY_COMPANIES_BASE_URL } from './TwentyCompaniesBaseUrl';
|
export { TWENTY_COMPANIES_BASE_URL } from './TwentyCompaniesBaseUrl';
|
||||||
|
export { TWENTY_DOCKER_HUB_LINK } from './TwentyDockerHubLink';
|
||||||
export { TWENTY_ICONS_BASE_URL } from './TwentyIconsBaseUrl';
|
export { TWENTY_ICONS_BASE_URL } from './TwentyIconsBaseUrl';
|
||||||
|
|||||||
Reference in New Issue
Block a user