mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 16:42:15 +00:00
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { asTrimmedString } from '@/utils/stringUtils';
|
|
|
|
export type DockerImageRegistryLink = {
|
|
href: string;
|
|
label: string;
|
|
};
|
|
|
|
const DOCKER_HUB_REGISTRIES = new Set(['docker.io', 'index.docker.io', 'registry-1.docker.io']);
|
|
|
|
const encodeRepositoryPath = (repository: string): string =>
|
|
repository
|
|
.split('/')
|
|
.filter(Boolean)
|
|
.map((segment) => encodeURIComponent(segment))
|
|
.join('/');
|
|
|
|
const stripImageTagAndDigest = (image: string): string => {
|
|
const withoutDigest = image.split('@', 1)[0] ?? '';
|
|
const lastSlash = withoutDigest.lastIndexOf('/');
|
|
const lastColon = withoutDigest.lastIndexOf(':');
|
|
return lastColon > lastSlash ? withoutDigest.slice(0, lastColon) : withoutDigest;
|
|
};
|
|
|
|
const hasExplicitRegistry = (firstSegment: string): boolean =>
|
|
firstSegment === 'localhost' || firstSegment.includes('.') || firstSegment.includes(':');
|
|
|
|
/**
|
|
* Returns a public tags page only when the registry has a stable, predictable
|
|
* browser URL. Private and unknown registries deliberately return null rather
|
|
* than producing a misleading or credential-leaking link.
|
|
*/
|
|
export function getDockerImageRegistryLink(
|
|
rawImage: string | null | undefined,
|
|
): DockerImageRegistryLink | null {
|
|
const image = asTrimmedString(rawImage);
|
|
if (!image || /[\s\\]/.test(image)) return null;
|
|
|
|
const reference = stripImageTagAndDigest(image);
|
|
const parts = reference.split('/').filter(Boolean);
|
|
if (parts.length === 0) return null;
|
|
|
|
const firstSegment = (parts[0] ?? '').toLowerCase();
|
|
const explicitRegistry = hasExplicitRegistry(firstSegment);
|
|
const registry = explicitRegistry ? firstSegment : 'docker.io';
|
|
const repositoryParts = explicitRegistry ? parts.slice(1) : parts;
|
|
if (repositoryParts.length === 0) return null;
|
|
|
|
if (DOCKER_HUB_REGISTRIES.has(registry)) {
|
|
const normalizedRepository =
|
|
repositoryParts.length === 1
|
|
? ['library', repositoryParts[0] ?? '']
|
|
: repositoryParts[0]?.toLowerCase() === 'library'
|
|
? ['library', ...repositoryParts.slice(1)]
|
|
: repositoryParts;
|
|
const repository = encodeRepositoryPath(normalizedRepository.join('/'));
|
|
if (!repository) return null;
|
|
|
|
if (normalizedRepository[0] === 'library' && normalizedRepository.length === 2) {
|
|
return {
|
|
href: `https://hub.docker.com/_/${encodeURIComponent(normalizedRepository[1] ?? '')}/tags`,
|
|
label: 'View image tags',
|
|
};
|
|
}
|
|
return {
|
|
href: `https://hub.docker.com/r/${repository}/tags`,
|
|
label: 'View image tags',
|
|
};
|
|
}
|
|
|
|
if (registry === 'quay.io') {
|
|
const repository = encodeRepositoryPath(repositoryParts.join('/'));
|
|
if (!repository) return null;
|
|
return {
|
|
href: `https://quay.io/repository/${repository}?tab=tags`,
|
|
label: 'View image tags',
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|