feat(releases-widget): add new providers, Github Packages, linuxserver.io and Quay (#3607)
Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -207,6 +207,27 @@ export const integrationDefs = {
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://codeberg.org",
|
||||
},
|
||||
linuxServerIO: {
|
||||
name: "LinuxServer.io",
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://api.linuxserver.io",
|
||||
},
|
||||
githubPackages: {
|
||||
name: "Github Packages",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://api.github.com",
|
||||
},
|
||||
quay: {
|
||||
name: "Quay",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://quay.io",
|
||||
},
|
||||
ntfy: {
|
||||
name: "ntfy",
|
||||
secretKinds: [["topic"], ["topic", "apiKey"]],
|
||||
|
||||
@@ -14,11 +14,13 @@ import { QBitTorrentIntegration } from "../download-client/qbittorrent/qbittorre
|
||||
import { SabnzbdIntegration } from "../download-client/sabnzbd/sabnzbd-integration";
|
||||
import { TransmissionIntegration } from "../download-client/transmission/transmission-integration";
|
||||
import { EmbyIntegration } from "../emby/emby-integration";
|
||||
import { GithubPackagesIntegration } from "../github-packages/github-packages-integration";
|
||||
import { GithubIntegration } from "../github/github-integration";
|
||||
import { GitlabIntegration } from "../gitlab/gitlab-integration";
|
||||
import { HomeAssistantIntegration } from "../homeassistant/homeassistant-integration";
|
||||
import { JellyfinIntegration } from "../jellyfin/jellyfin-integration";
|
||||
import { JellyseerrIntegration } from "../jellyseerr/jellyseerr-integration";
|
||||
import { LinuxServerIOIntegration } from "../linuxserverio/linuxserverio-integration";
|
||||
import { LidarrIntegration } from "../media-organizer/lidarr/lidarr-integration";
|
||||
import { RadarrIntegration } from "../media-organizer/radarr/radarr-integration";
|
||||
import { ReadarrIntegration } from "../media-organizer/readarr/readarr-integration";
|
||||
@@ -34,6 +36,7 @@ import { createPiHoleIntegrationAsync } from "../pi-hole/pi-hole-integration-fac
|
||||
import { PlexIntegration } from "../plex/plex-integration";
|
||||
import { ProwlarrIntegration } from "../prowlarr/prowlarr-integration";
|
||||
import { ProxmoxIntegration } from "../proxmox/proxmox-integration";
|
||||
import { QuayIntegration } from "../quay/quay-integration";
|
||||
import { UnifiControllerIntegration } from "../unifi-controller/unifi-controller-integration";
|
||||
import type { Integration, IntegrationInput } from "./integration";
|
||||
|
||||
@@ -104,6 +107,9 @@ export const integrationCreators = {
|
||||
gitlab: GitlabIntegration,
|
||||
npm: NPMIntegration,
|
||||
codeberg: CodebergIntegration,
|
||||
linuxServerIO: LinuxServerIOIntegration,
|
||||
githubPackages: GithubPackagesIntegration,
|
||||
quay: QuayIntegration,
|
||||
ntfy: NTFYIntegration,
|
||||
mock: MockIntegration,
|
||||
} satisfies Record<IntegrationKind, IntegrationInstance | [(input: IntegrationInput) => Promise<Integration>]>;
|
||||
|
||||
@@ -20,7 +20,7 @@ const localLogger = logger.child({ module: "CodebergIntegration" });
|
||||
|
||||
export class CodebergIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
|
||||
if (!this.hasSecretValue("personalAccessToken")) return await callback({});
|
||||
if (!this.hasSecretValue("personalAccessToken")) return await callback(undefined);
|
||||
|
||||
return await callback({
|
||||
Authorization: `token ${this.getSecretValue("personalAccessToken")}`,
|
||||
@@ -61,7 +61,7 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
|
||||
const details = await this.getDetailsAsync(owner, name);
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return fetchWithTrustedCertificatesAsync(
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
this.url(`/api/v1/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/releases`),
|
||||
{ headers },
|
||||
);
|
||||
|
||||
@@ -30,7 +30,8 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
|
||||
}
|
||||
|
||||
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
|
||||
if (!this.hasSecretValue("username") || !this.hasSecretValue("personalAccessToken")) return await callback({});
|
||||
if (!this.hasSecretValue("username") || !this.hasSecretValue("personalAccessToken"))
|
||||
return await callback(undefined);
|
||||
|
||||
const storedSession = await this.sessionStore.getAsync();
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Octokit, RequestError } from "octokit";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GithubPackagesIntegration" });
|
||||
|
||||
export class GithubPackagesIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
private static readonly userAgent = "Homarr-Lab/Homarr:GithubPackagesIntegration";
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const headers: RequestInit["headers"] = {
|
||||
"User-Agent": GithubPackagesIntegration.userAgent,
|
||||
};
|
||||
|
||||
if (this.hasSecretValue("personalAccessToken"))
|
||||
headers.Authorization = `Bearer ${this.getSecretValue("personalAccessToken")}`;
|
||||
|
||||
const response = await input.fetchAsync(this.url("/octocat"), {
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with Github Packages integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const api = this.getApi();
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.rest.packages.getAllPackageVersionsForPackageOwnedByUser({
|
||||
username: owner,
|
||||
package_type: "container",
|
||||
package_name: name,
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const releasesProviderResponse = releasesResponse.data.reduce<ReleaseProviderResponse[]>((acc, release) => {
|
||||
if (!release.metadata?.container?.tags || !(release.metadata.container.tags.length > 0)) return acc;
|
||||
|
||||
release.metadata.container.tags.forEach((tag) => {
|
||||
acc.push({
|
||||
latestRelease: tag,
|
||||
latestReleaseAt: new Date(release.updated_at),
|
||||
releaseUrl: release.html_url,
|
||||
releaseDescription: release.description ?? undefined,
|
||||
});
|
||||
});
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof RequestError ? error.message : String(error);
|
||||
|
||||
localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github Packages integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: errorMessage },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected async getDetailsAsync(
|
||||
api: Octokit,
|
||||
owner: string,
|
||||
name: string,
|
||||
): Promise<DetailsProviderResponse | undefined> {
|
||||
try {
|
||||
const response = await api.rest.packages.getPackageForUser({
|
||||
username: owner,
|
||||
package_type: "container",
|
||||
package_name: name,
|
||||
});
|
||||
|
||||
return {
|
||||
projectUrl: response.data.repository?.html_url ?? response.data.html_url,
|
||||
projectDescription: response.data.repository?.description ?? undefined,
|
||||
isFork: response.data.repository?.fork,
|
||||
isArchived: response.data.repository?.archived,
|
||||
createdAt: new Date(response.data.created_at),
|
||||
starsCount: response.data.repository?.stargazers_count,
|
||||
openIssues: response.data.repository?.open_issues_count,
|
||||
forksCount: response.data.repository?.forks_count,
|
||||
};
|
||||
} catch (error) {
|
||||
localLogger.warn(`Failed to get details for ${owner}\\${name} with Github Packages integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: error instanceof RequestError ? error.message : String(error),
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private getApi() {
|
||||
return new Octokit({
|
||||
baseUrl: this.url("/").origin,
|
||||
request: {
|
||||
fetch: fetchWithTrustedCertificatesAsync,
|
||||
},
|
||||
userAgent: GithubPackagesIntegration.userAgent,
|
||||
throttle: { enabled: false }, // Disable throttling for this integration, Octokit will retry by default after a set time, thus delaying the repsonse to the user in case of errors. Errors will be shown to the user, no need to retry the request.
|
||||
...(this.hasSecretValue("personalAccessToken") ? { auth: this.getSecretValue("personalAccessToken") } : {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
|
||||
}
|
||||
|
||||
const api = this.getApi();
|
||||
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type { ReleasesRepository, ReleasesResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./linuxserverio-schemas";
|
||||
|
||||
const localLogger = logger.child({ module: "LinuxServerIOsIntegration" });
|
||||
|
||||
export class LinuxServerIOIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await input.fetchAsync(this.url("/health"));
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/images"));
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
|
||||
if (!release) {
|
||||
localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
|
||||
owner,
|
||||
name,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
latestRelease: release.version,
|
||||
latestReleaseAt: release.version_timestamp,
|
||||
releaseDescription: release.changelog?.shift()?.desc,
|
||||
projectUrl: release.github_url,
|
||||
projectDescription: release.description,
|
||||
isArchived: release.deprecated,
|
||||
createdAt: release.initial_date ? new Date(release.initial_date) : undefined,
|
||||
starsCount: release.stars,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const releasesResponseSchema = z.object({
|
||||
data: z.object({
|
||||
repositories: z.object({
|
||||
linuxserver: z.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
initial_date: z
|
||||
.string()
|
||||
.transform((value) => new Date(value))
|
||||
.optional(),
|
||||
github_url: z.string(),
|
||||
description: z.string(),
|
||||
version: z.string(),
|
||||
version_timestamp: z.string().transform((value) => new Date(value)),
|
||||
stars: z.number(),
|
||||
deprecated: z.boolean(),
|
||||
changelog: z
|
||||
.array(
|
||||
z.object({
|
||||
date: z.string().transform((value) => new Date(value)),
|
||||
desc: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
109
packages/integrations/src/quay/quay-integration.ts
Normal file
109
packages/integrations/src/quay/quay-integration.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import type { RequestInit, Response } from "undici";
|
||||
|
||||
import { fetchWithTrustedCertificatesAsync } from "@homarr/certificates/server";
|
||||
import { logger } from "@homarr/log";
|
||||
|
||||
import type { IntegrationTestingInput } from "../base/integration";
|
||||
import { Integration } from "../base/integration";
|
||||
import { TestConnectionError } from "../base/test-connection/test-connection-error";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { ReleasesProviderIntegration } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./quay-schemas";
|
||||
|
||||
const localLogger = logger.child({ module: "QuayIntegration" });
|
||||
|
||||
export class QuayIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
private async withHeadersAsync(callback: (headers: RequestInit["headers"]) => Promise<Response>): Promise<Response> {
|
||||
if (!this.hasSecretValue("personalAccessToken")) return await callback(undefined);
|
||||
|
||||
return await callback({
|
||||
Authorization: `token ${this.getSecretValue("personalAccessToken")}`,
|
||||
});
|
||||
}
|
||||
|
||||
protected async testingAsync(input: IntegrationTestingInput): Promise<TestingResult> {
|
||||
const response = await this.withHeadersAsync(async (headers) => {
|
||||
return await input.fetchAsync(this.url("/api/v1/discovery"), {
|
||||
headers,
|
||||
});
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
return TestConnectionError.StatusResult(response);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
this.url(
|
||||
`/api/v1/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}?includeTags=true&includeStats=true`,
|
||||
),
|
||||
{
|
||||
headers,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const details = {
|
||||
projectDescription: data.description,
|
||||
};
|
||||
|
||||
const releasesProviderResponse = Object.entries(data.tags).reduce<ReleaseProviderResponse[]>((acc, [_, tag]) => {
|
||||
if (!tag.name || !tag.last_modified) return acc;
|
||||
|
||||
acc.push({
|
||||
latestRelease: tag.name,
|
||||
latestReleaseAt: new Date(tag.last_modified),
|
||||
releaseUrl: `https://quay.io/repository/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/tag/${encodeURIComponent(tag.name)}`,
|
||||
});
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
packages/integrations/src/quay/quay-schemas.ts
Normal file
11
packages/integrations/src/quay/quay-schemas.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const releasesResponseSchema = z.object({
|
||||
description: z.string().optional(),
|
||||
tags: z.record(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
last_modified: z.string(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
@@ -570,7 +570,7 @@ const ImportRepositorySelect = ({
|
||||
)}
|
||||
</Group>
|
||||
|
||||
<Tooltip label={tRepository("noProvider.tooltip")} disabled={!integration} withArrow>
|
||||
<Tooltip label={tRepository("noProvider.tooltip")} disabled={integration !== undefined} withArrow>
|
||||
<Group>
|
||||
{integration ? (
|
||||
<MaskedImage
|
||||
@@ -618,29 +618,19 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
|
||||
() =>
|
||||
docker.data?.containers.reduce<ReleasesRepositoryImport[]>((acc, container) => {
|
||||
const [maybeSource, maybeIdentifierAndVersion] = container.image.split(/\/(.*)/);
|
||||
const hasSource = maybeSource && maybeSource in sourceToProviderKind;
|
||||
const hasSource = maybeSource && maybeSource in containerImageToProviderKind;
|
||||
const source = hasSource ? maybeSource : "docker.io";
|
||||
const identifierAndVersion = hasSource ? maybeIdentifierAndVersion : container.image;
|
||||
const [identifier, version] =
|
||||
hasSource && maybeIdentifierAndVersion ? maybeIdentifierAndVersion.split(":") : container.image.split(":");
|
||||
|
||||
if (!identifierAndVersion) return acc;
|
||||
if (!identifier) return acc;
|
||||
|
||||
const providerKey = sourceToProviderKind[source];
|
||||
const providerKind = containerImageToProviderKind[source] ?? "dockerHub";
|
||||
const integrationId = Object.values(innerProps.integrations).find(
|
||||
(integration) => integration.kind === providerKey,
|
||||
(integration) => integration.kind === providerKind,
|
||||
)?.id;
|
||||
|
||||
const [identifier, version] = identifierAndVersion.split(":");
|
||||
|
||||
if (!identifier || !integrationId) return acc;
|
||||
|
||||
if (
|
||||
acc.some(
|
||||
(item) =>
|
||||
item.providerIntegrationId !== undefined &&
|
||||
innerProps.integrations[item.providerIntegrationId]?.kind === providerKey &&
|
||||
item.identifier === identifier,
|
||||
)
|
||||
)
|
||||
if (acc.some((item) => item.providerIntegrationId === integrationId && item.identifier === identifier))
|
||||
return acc;
|
||||
|
||||
acc.push({
|
||||
@@ -651,10 +641,7 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
|
||||
name: formatIdentifierName(identifier),
|
||||
versionFilter: version ? parseImageVersionToVersionFilter(version) : undefined,
|
||||
alreadyImported: innerProps.repositories.some(
|
||||
(item) =>
|
||||
item.providerIntegrationId !== undefined &&
|
||||
innerProps.integrations[item.providerIntegrationId]?.kind === providerKey &&
|
||||
item.identifier === identifier,
|
||||
(item) => item.providerIntegrationId === integrationId && item.identifier === identifier,
|
||||
),
|
||||
});
|
||||
return acc;
|
||||
@@ -811,9 +798,11 @@ const RepositoryImportModal = createModal<RepositoryImportProps>(({ innerProps,
|
||||
size: "xl",
|
||||
});
|
||||
|
||||
const sourceToProviderKind: Record<string, IntegrationKind> = {
|
||||
const containerImageToProviderKind: Record<string, IntegrationKind> = {
|
||||
"ghcr.io": "github",
|
||||
"docker.io": "dockerHub",
|
||||
"lscr.io": "linuxServerIO",
|
||||
"quay.io": "quay",
|
||||
};
|
||||
|
||||
const parseImageVersionToVersionFilter = (imageVersion: string): ReleasesVersionFilter | undefined => {
|
||||
|
||||
Reference in New Issue
Block a user