1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 13:55:21 +02:00

refactor(ui/image-config): create react component [EE-5342] (#8856)

This commit is contained in:
Chaim Lev-Ari 2023-07-10 18:56:12 +03:00 committed by GitHub
parent bf51f1b6c9
commit 10014ae171
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1464 additions and 84 deletions

View file

@ -0,0 +1,15 @@
import { Registry } from '@/react/portainer/registries/types/registry';
/**
* Encodes the registry credentials in base64
* @param registryId
* @returns
*/
export function encodeRegistryCredentials(registryId: Registry['Id']) {
const credentials = {
registryId,
};
const buf = Buffer.from(JSON.stringify(credentials));
return buf.toString('base64url');
}

View file

@ -0,0 +1,9 @@
import { EnvironmentId } from '@/react/portainer/environments/types';
import { queryKeys as dockerQueryKeys } from '../../queries/utils';
export const queryKeys = {
base: (environmentId: EnvironmentId) =>
[dockerQueryKeys.root(environmentId), 'images'] as const,
list: (environmentId: EnvironmentId) => queryKeys.base(environmentId),
};

View file

@ -0,0 +1,124 @@
import { useQuery } from 'react-query';
import axios, { parseAxiosError } from '@/portainer/services/axios';
import { EnvironmentId } from '@/react/portainer/environments/types';
import { buildUrl } from '../../proxy/queries/build-url';
import { queryKeys } from './queryKeys';
interface ImageSummary {
/**
* Number of containers using this image. Includes both stopped and running containers.
*
* This size is not calculated by default, and depends on which API endpoint is used.
* `-1` indicates that the value has not been set / calculated.
*
* Required: true
*/
Containers: number;
/**
* Date and time at which the image was created as a Unix timestamp
* (number of seconds sinds EPOCH).
*
* Required: true
*/
Created: number;
/**
* ID is the content-addressable ID of an image.
*
* This identifier is a content-addressable digest calculated from the
* image's configuration (which includes the digests of layers used by
* the image).
*
* Note that this digest differs from the `RepoDigests` below, which
* holds digests of image manifests that reference the image.
*
* Required: true
*/
Id: string;
/**
* User-defined key/value metadata.
* Required: true
*/
Labels: { [key: string]: string };
/**
* ID of the parent image.
*
* Depending on how the image was created, this field may be empty and
* is only set for images that were built/created locally. This field
* is empty if the image was pulled from an image registry.
*
* Required: true
*/
ParentId: string;
/**
* List of content-addressable digests of locally available image manifests
* that the image is referenced from. Multiple manifests can refer to the
* same image.
*
* These digests are usually only available if the image was either pulled
* from a registry, or if the image was pushed to a registry, which is when
* the manifest is generated and its digest calculated.
*
* Required: true
*/
RepoDigests: string[];
/**
* List of image names/tags in the local image cache that reference this
* image.
*
* Multiple image tags can refer to the same image, and this list may be
* empty if no tags reference the image, in which case the image is
* "untagged", in which case it can still be referenced by its ID.
*
* Required: true
*/
RepoTags: string[];
/**
* Total size of image layers that are shared between this image and other
* images.
*
* This size is not calculated by default. `-1` indicates that the value
* has not been set / calculated.
*
* Required: true
*/
SharedSize: number;
Size: number;
VirtualSize: number;
}
type ImagesListResponse = ImageSummary[];
export function useImages<T = ImagesListResponse>(
environmentId: EnvironmentId,
{
select,
enabled,
}: { select?(data: ImagesListResponse): T; enabled?: boolean } = {}
) {
return useQuery(
queryKeys.list(environmentId),
() => getImages(environmentId),
{ select, enabled }
);
}
async function getImages(environmentId: EnvironmentId) {
try {
const { data } = await axios.get<ImagesListResponse>(
buildUrl(environmentId, 'images', 'json')
);
return data;
} catch (err) {
throw parseAxiosError(err as Error, 'Unable to retrieve images');
}
}

View file

@ -0,0 +1,22 @@
import { DockerImageResponse } from './types/response';
type Status = 'outdated' | 'updated' | 'inprocess' | string;
export enum ResourceType {
CONTAINER,
SERVICE,
}
export interface ImageStatus {
Status: Status;
Message: string;
}
export type ResourceID = string;
type DecoratedDockerImage = {
Used: boolean;
};
export type DockerImage = DecoratedDockerImage &
Omit<DockerImageResponse, keyof DecoratedDockerImage>;

View file

@ -0,0 +1,12 @@
export type DockerImageResponse = {
Containers: number;
Created: number;
Id: string;
Labels: { [key: string]: string };
ParentId: string;
RepoDigests: string[];
RepoTags: string[];
SharedSize: number;
Size: number;
VirtualSize: number;
};

View file

@ -0,0 +1,101 @@
import _ from 'lodash';
import { trimSHA } from '@/docker/filters/utils';
import {
Registry,
RegistryTypes,
} from '@/react/portainer/registries/types/registry';
import { DockerImage } from './types';
import { DockerImageResponse } from './types/response';
export function parseViewModel(response: DockerImageResponse): DockerImage {
return {
...response,
Used: false,
RepoTags:
response.RepoTags ??
response.RepoDigests.map((digest) => `${trimSHA(digest)}:<none>`),
};
}
export function getUniqueTagListFromImages(
images: Array<{ RepoTags?: string[] }>
) {
return _.uniq(
images.flatMap((image) =>
image.RepoTags
? image.RepoTags.filter((item) => !item.includes('<none>'))
: []
)
);
}
export function imageContainsURL(image: string) {
const split = image.split('/');
const url = split[0];
if (split.length > 1) {
return url.includes('.') || url.includes(':');
}
return false;
}
/**
* builds the complete uri for an image based on its registry
* @param {PorImageRegistryModel} imageModel
*/
export function buildImageFullURI(image: string, registry?: Registry) {
if (!registry) {
return ensureTag(image);
}
const imageName = buildImageFullURIWithRegistry(image, registry);
return ensureTag(imageName);
function ensureTag(image: string, defaultTag = 'latest') {
return image.includes(':') ? image : `${image}:${defaultTag}`;
}
}
function buildImageFullURIWithRegistry(image: string, registry: Registry) {
switch (registry.Type) {
case RegistryTypes.GITHUB:
return buildImageURIForGithub(image, registry);
case RegistryTypes.GITLAB:
return buildImageURIForGitLab(image, registry);
case RegistryTypes.QUAY:
return buildImageURIForQuay(image, registry);
case RegistryTypes.ANONYMOUS:
return image;
default:
return buildImageURIForOtherRegistry(image, registry);
}
function buildImageURIForGithub(image: string, registry: Registry) {
const imageName = image.split('/').pop();
const namespace = registry.Github.UseOrganisation
? registry.Github.OrganisationName
: registry.Username;
return `${registry.URL}/${namespace}/${imageName}`;
}
function buildImageURIForGitLab(image: string, registry: Registry) {
const slash = image.startsWith(':') ? '' : '/';
return `${registry.URL}/${registry.Gitlab.ProjectPath}${slash}${image}`;
}
function buildImageURIForQuay(image: string, registry: Registry) {
const name = registry.Quay.UseOrganisation
? registry.Quay.OrganisationName
: registry.Username;
const url = registry.URL ? `${registry.URL}/` : '';
return `${url}${name}/${image}`;
}
function buildImageURIForOtherRegistry(image: string, registry: Registry) {
const url = registry.URL ? `${registry.URL}/` : '';
return url + image;
}
}