chore(release): automatic release v1.43.1
This commit is contained in:
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
1
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -33,6 +33,7 @@ body:
|
||||
options:
|
||||
# The below comment is used to insert a new version with on-release.yml
|
||||
#NEXT_VERSION#
|
||||
- 1.43.0
|
||||
- 1.42.1
|
||||
- 1.42.0
|
||||
- 1.41.0
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
- uses: actions/setup-node@v5
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
with:
|
||||
node-version: 22.20.0
|
||||
node-version: 24.10.0
|
||||
cache: "pnpm"
|
||||
- run: npm i -g pnpm
|
||||
if: env.SKIP_RELEASE == 'false'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:22.20.0-alpine AS base
|
||||
FROM node:24.10.0-alpine AS base
|
||||
|
||||
FROM base AS builder
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
@@ -61,10 +61,10 @@
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@tanstack/react-query-devtools": "^5.90.2",
|
||||
"@tanstack/react-query-next-experimental": "^5.90.2",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/next": "^11.6.0",
|
||||
"@trpc/react-query": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"@trpc/client": "^11.7.0",
|
||||
"@trpc/next": "^11.7.0",
|
||||
"@trpc/react-query": "^11.7.0",
|
||||
"@trpc/server": "^11.7.0",
|
||||
"@xterm/addon-canvas": "^0.7.0",
|
||||
"@xterm/addon-fit": "0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
@@ -84,7 +84,7 @@
|
||||
"react-error-boundary": "^6.0.0",
|
||||
"react-simple-code-editor": "^0.14.1",
|
||||
"sass": "^1.93.2",
|
||||
"superjson": "2.2.2",
|
||||
"superjson": "2.2.3",
|
||||
"swagger-ui-react": "^5.29.5",
|
||||
"use-deep-compare-effect": "^1.8.1",
|
||||
"zod": "^4.1.12"
|
||||
@@ -94,7 +94,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/chroma-js": "3.1.1",
|
||||
"@types/node": "^22.18.11",
|
||||
"@types/node": "^24.9.1",
|
||||
"@types/prismjs": "^1.26.5",
|
||||
"@types/react": "19.2.2",
|
||||
"@types/react-dom": "19.2.2",
|
||||
|
||||
@@ -40,14 +40,14 @@
|
||||
"dayjs": "^1.11.18",
|
||||
"dotenv": "^17.2.3",
|
||||
"fastify": "^5.6.1",
|
||||
"superjson": "2.2.2",
|
||||
"superjson": "2.2.3",
|
||||
"undici": "7.16.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/node": "^22.18.11",
|
||||
"@types/node": "^24.9.1",
|
||||
"dotenv-cli": "^10.0.0",
|
||||
"esbuild": "^0.25.11",
|
||||
"eslint": "^9.38.0",
|
||||
|
||||
20
package.json
20
package.json
@@ -39,28 +39,28 @@
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^13.0.1",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@semantic-release/github": "^11.0.6",
|
||||
"@semantic-release/npm": "^12.0.2",
|
||||
"@semantic-release/github": "^12.0.0",
|
||||
"@semantic-release/npm": "^13.1.1",
|
||||
"@semantic-release/release-notes-generator": "^14.1.0",
|
||||
"@testcontainers/redis": "^11.7.1",
|
||||
"@testcontainers/redis": "^11.7.2",
|
||||
"@turbo/gen": "^2.5.8",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/ui": "^3.2.4",
|
||||
"conventional-changelog-conventionalcommits": "^9.1.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"jsdom": "^27.0.1",
|
||||
"prettier": "^3.6.2",
|
||||
"semantic-release": "^24.2.9",
|
||||
"testcontainers": "^11.7.1",
|
||||
"semantic-release": "^25.0.1",
|
||||
"testcontainers": "^11.7.2",
|
||||
"turbo": "^2.5.8",
|
||||
"typescript": "^5.9.3",
|
||||
"vite-tsconfig-paths": "^5.1.4",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"packageManager": "pnpm@10.18.3",
|
||||
"packageManager": "pnpm@10.19.0",
|
||||
"engines": {
|
||||
"node": ">=22.20.0"
|
||||
"node": ">=22.21.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
@@ -82,7 +82,7 @@
|
||||
"brace-expansion@>=1.0.0 <=1.1.11": ">=4.0.1",
|
||||
"esbuild@<=0.24.2": ">=0.25.11",
|
||||
"form-data@>=4.0.0 <4.0.4": ">=4.0.4",
|
||||
"hono@<4.6.5": ">=4.10.2",
|
||||
"hono@<4.6.5": ">=4.10.3",
|
||||
"linkifyjs@<4.3.2": ">=4.3.2",
|
||||
"nanoid@>=4.0.0 <5.0.9": ">=5.1.6",
|
||||
"prismjs@<1.30.0": ">=1.30.0",
|
||||
@@ -93,7 +93,7 @@
|
||||
"tar-fs@>=3.0.0 <3.0.9": ">=3.1.1",
|
||||
"tar-fs@>=2.0.0 <2.1.3": ">=3.1.1",
|
||||
"tmp@<=0.2.3": ">=0.2.5",
|
||||
"vite@>=5.0.0 <=5.4.18": ">=7.1.11"
|
||||
"vite@>=5.0.0 <=5.4.18": ">=7.1.12"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@types/node-unifi": "patches/@types__node-unifi.patch",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@umami/node": "^0.4.0",
|
||||
"superjson": "2.2.2"
|
||||
"superjson": "2.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -44,15 +44,15 @@
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@kubernetes/client-node": "^1.4.0",
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/react-query": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"@trpc/tanstack-react-query": "^11.6.0",
|
||||
"@trpc/client": "^11.7.0",
|
||||
"@trpc/react-query": "^11.7.0",
|
||||
"@trpc/server": "^11.7.0",
|
||||
"@trpc/tanstack-react-query": "^11.7.0",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"next": "15.5.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"superjson": "2.2.2",
|
||||
"superjson": "2.2.3",
|
||||
"trpc-to-openapi": "^3.1.0",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
|
||||
@@ -40,15 +40,22 @@ export const releasesRouter = createTRPCRouter({
|
||||
.query(async ({ ctx, input }) => {
|
||||
return await Promise.all(
|
||||
input.repositories.map(async (repository) => {
|
||||
const innerHandler = releasesRequestHandler.handler(ctx.integration, {
|
||||
id: repository.id,
|
||||
identifier: repository.identifier,
|
||||
versionRegex: formatVersionFilterRegex(repository.versionFilter),
|
||||
});
|
||||
const response = await releasesRequestHandler
|
||||
.handler(ctx.integration, {
|
||||
id: repository.id,
|
||||
identifier: repository.identifier,
|
||||
versionRegex: formatVersionFilterRegex(repository.versionFilter),
|
||||
})
|
||||
.getCachedOrUpdatedDataAsync({
|
||||
forceUpdate: false,
|
||||
});
|
||||
|
||||
return await innerHandler.getCachedOrUpdatedDataAsync({
|
||||
forceUpdate: false,
|
||||
});
|
||||
return {
|
||||
id: repository.id,
|
||||
integration: { name: ctx.integration.name, kind: ctx.integration.kind },
|
||||
timestamp: response.timestamp,
|
||||
...response.data,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.41.0",
|
||||
"@auth/drizzle-adapter": "^1.11.0",
|
||||
"@auth/core": "^0.41.1",
|
||||
"@auth/drizzle-adapter": "^1.11.1",
|
||||
"@homarr/certificates": "workspace:^0.1.0",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
@@ -36,7 +36,7 @@
|
||||
"cookies": "^0.9.1",
|
||||
"ldapts": "8.0.9",
|
||||
"next": "15.5.6",
|
||||
"next-auth": "5.0.0-beta.29",
|
||||
"next-auth": "5.0.0-beta.30",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"zod": "^4.1.12"
|
||||
@@ -46,7 +46,7 @@
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/bcrypt": "6.0.0",
|
||||
"@types/cookies": "0.9.1",
|
||||
"@types/cookies": "0.9.2",
|
||||
"eslint": "^9.38.0",
|
||||
"prettier": "^3.6.2",
|
||||
"typescript": "^5.9.3"
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"dependencies": {
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"dayjs": "^1.11.18",
|
||||
"dns-caching": "^0.2.7",
|
||||
"next": "15.5.6",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@t3-oss/env-nextjs": "^0.13.8",
|
||||
"ioredis": "5.8.1",
|
||||
"ioredis": "5.8.2",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
"@homarr/cron-jobs": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@tanstack/react-query": "^5.90.5",
|
||||
"@trpc/client": "^11.6.0",
|
||||
"@trpc/server": "^11.6.0",
|
||||
"@trpc/tanstack-react-query": "^11.6.0",
|
||||
"@trpc/client": "^11.7.0",
|
||||
"@trpc/server": "^11.7.0",
|
||||
"@trpc/tanstack-react-query": "^11.7.0",
|
||||
"node-cron": "^4.2.1",
|
||||
"react": "19.2.0",
|
||||
"zod": "^4.1.12"
|
||||
|
||||
@@ -43,24 +43,24 @@
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.41.0",
|
||||
"@auth/core": "^0.41.1",
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"@homarr/definitions": "workspace:^0.1.0",
|
||||
"@homarr/log": "workspace:^0.1.0",
|
||||
"@homarr/server-settings": "workspace:^0.1.0",
|
||||
"@mantine/core": "^8.3.5",
|
||||
"@paralleldrive/cuid2": "^2.2.2",
|
||||
"@testcontainers/mysql": "^11.7.1",
|
||||
"@testcontainers/postgresql": "^11.7.1",
|
||||
"@paralleldrive/cuid2": "^3.1.0",
|
||||
"@testcontainers/mysql": "^11.7.2",
|
||||
"@testcontainers/postgresql": "^11.7.2",
|
||||
"better-sqlite3": "^12.4.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-kit": "^0.31.5",
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"mysql2": "3.15.2",
|
||||
"mysql2": "3.15.3",
|
||||
"pg": "^8.16.3",
|
||||
"superjson": "2.2.2"
|
||||
"superjson": "2.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"@types/dockerode": "^3.3.44",
|
||||
"@types/dockerode": "^3.3.45",
|
||||
"eslint": "^9.38.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"@homarr/translation": "workspace:^0.1.0",
|
||||
"@homarr/validation": "workspace:^0.1.0",
|
||||
"@jellyfin/sdk": "^0.11.0",
|
||||
"@jellyfin/sdk": "^0.12.0",
|
||||
"@octokit/auth-app": "^8.1.1",
|
||||
"ical.js": "^2.2.1",
|
||||
"maria2": "^0.4.1",
|
||||
"node-ical": "^0.22.0",
|
||||
"node-ical": "^0.22.1",
|
||||
"octokit": "^5.0.4",
|
||||
"proxmox-api": "1.1.1",
|
||||
"tsdav": "^2.1.5",
|
||||
|
||||
@@ -11,8 +11,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { detailsResponseSchema, releasesResponseSchema } from "./codeberg-schemas";
|
||||
|
||||
@@ -43,22 +42,23 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with Codeberg integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
`Invalid identifier format. Expected 'owner/name', for ${identifier} with Codeberg integration`,
|
||||
{ identifier },
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
const details = await this.getDetailsAsync(owner, name);
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
@@ -66,34 +66,36 @@ export class CodebergIntegration extends Integration implements ReleasesProvider
|
||||
{ headers },
|
||||
);
|
||||
});
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
success: false,
|
||||
error: {
|
||||
code: "unexpected",
|
||||
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const formattedReleases = data.map((tag) => ({
|
||||
latestRelease: tag.tag_name,
|
||||
latestReleaseAt: tag.published_at,
|
||||
releaseUrl: tag.url,
|
||||
releaseDescription: tag.body,
|
||||
isPreRelease: tag.prerelease,
|
||||
}));
|
||||
return getLatestRelease(formattedReleases, repository, details);
|
||||
}
|
||||
|
||||
const formattedReleases = data.map((tag) => ({
|
||||
latestRelease: tag.tag_name,
|
||||
latestReleaseAt: tag.published_at,
|
||||
releaseUrl: tag.url,
|
||||
releaseDescription: tag.body,
|
||||
isPreRelease: tag.prerelease,
|
||||
}));
|
||||
|
||||
const latestRelease = getLatestRelease(formattedReleases, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
const details = await this.getDetailsAsync(owner, name);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
}
|
||||
|
||||
protected async getDetailsAsync(owner: string, name: string): Promise<DetailsProviderResponse | undefined> {
|
||||
|
||||
@@ -14,8 +14,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { accessTokenResponseSchema, detailsResponseSchema, releasesResponseSchema } from "./docker-hub-schemas";
|
||||
|
||||
@@ -73,49 +72,61 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const relativeUrl = this.getRelativeUrl(repository.identifier);
|
||||
if (relativeUrl === "/") {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name' or 'name', for ${repository.identifier} on DockerHub`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
}
|
||||
|
||||
const details = await this.getDetailsAsync(relativeUrl);
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/tags?page_size=100`), {
|
||||
headers,
|
||||
private parseIdentifier(identifier: string) {
|
||||
if (!identifier.includes("/")) return { owner: "", name: identifier };
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(`Invalid identifier format. Expected 'owner/name' or 'name', for ${identifier} on DockerHub`, {
|
||||
identifier,
|
||||
});
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
|
||||
const relativeUrl: `/${string}` = owner
|
||||
? `/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`
|
||||
: `/v2/repositories/library/${encodeURIComponent(name)}`;
|
||||
|
||||
for (let page = 0; page <= 5; page++) {
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(this.url(`${relativeUrl}/tags?page_size=100&page=${page}`), {
|
||||
headers,
|
||||
});
|
||||
});
|
||||
if (!releasesResponse.ok) {
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const releasesResult = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
if (!releasesResult.success) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: "unexpected",
|
||||
message: releasesResponseJson
|
||||
? JSON.stringify(releasesResponseJson, null, 2)
|
||||
: releasesResult.error.message,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const latestRelease = getLatestRelease(releasesResult.data.results, versionRegex);
|
||||
if (!latestRelease) continue;
|
||||
|
||||
const details = await this.getDetailsAsync(relativeUrl);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const releasesResult = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!releasesResult.success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: {
|
||||
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : releasesResult.error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return getLatestRelease(releasesResult.data.results, repository, details);
|
||||
}
|
||||
return { success: false, error: { code: "noMatchingVersion" } };
|
||||
}
|
||||
|
||||
private async getDetailsAsync(relativeUrl: `/${string}`): Promise<DetailsProviderResponse | undefined> {
|
||||
@@ -154,18 +165,6 @@ export class DockerHubIntegration extends Integration implements ReleasesProvide
|
||||
};
|
||||
}
|
||||
|
||||
private getRelativeUrl(identifier: string): `/${string}` {
|
||||
if (identifier.indexOf("/") > 0) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
return "/";
|
||||
}
|
||||
return `/v2/namespaces/${encodeURIComponent(owner)}/repositories/${encodeURIComponent(name)}`;
|
||||
} else {
|
||||
return `/v2/repositories/library/${encodeURIComponent(identifier)}`;
|
||||
}
|
||||
}
|
||||
|
||||
private async getSessionAsync(fetchAsync: typeof fetch = fetchWithTrustedCertificatesAsync): Promise<string> {
|
||||
const response = await fetchAsync(this.url("/v2/auth/token"), {
|
||||
method: "POST",
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { TestingResult } from "../base/test-connection/test-connection-serv
|
||||
import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration";
|
||||
import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types";
|
||||
import { convertJellyfinType } from "../jellyfin/jellyfin-integration";
|
||||
import type { IMediaReleasesIntegration, MediaRelease } from "../types";
|
||||
import type { IMediaReleasesIntegration, MediaRelease, MediaType } from "../types";
|
||||
|
||||
const sessionSchema = z.object({
|
||||
NowPlayingItem: z
|
||||
@@ -163,7 +163,7 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
|
||||
|
||||
return items.map((item) => ({
|
||||
id: item.Id,
|
||||
type: item.Type === "Movie" ? "movie" : item.Type === "Series" ? "tv" : "unknown",
|
||||
type: this.mapMediaReleaseType(item.Type),
|
||||
title: item.Name,
|
||||
subtitle: item.Taglines.at(0),
|
||||
description: item.Overview,
|
||||
@@ -179,6 +179,27 @@ export class EmbyIntegration extends Integration implements IMediaServerIntegrat
|
||||
}));
|
||||
}
|
||||
|
||||
private mapMediaReleaseType(type: string | undefined): MediaType {
|
||||
switch (type) {
|
||||
case "Audio":
|
||||
case "AudioBook":
|
||||
case "MusicAlbum":
|
||||
return "music";
|
||||
case "Book":
|
||||
return "book";
|
||||
case "Episode":
|
||||
case "Series":
|
||||
case "Season":
|
||||
return "tv";
|
||||
case "Movie":
|
||||
return "movie";
|
||||
case "Video":
|
||||
return "video";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// https://dev.emby.media/reference/RestAPI/UserService/getUsersPublic.html
|
||||
private async fetchUsersPublicAsync(): Promise<{ id: string; name: string }[]> {
|
||||
const apiKey = super.getSecretValue("apiKey");
|
||||
|
||||
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GitHubContainerRegistryIntegration" });
|
||||
@@ -43,23 +42,24 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with GitHub Container Registry integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
`Invalid identifier format. Expected 'owner/name', for ${identifier} with GitHub Container Registry integration`,
|
||||
{ identifier },
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
const api = this.getApi();
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.rest.packages.getAllPackageVersionsForPackageOwnedByUser({
|
||||
@@ -83,20 +83,20 @@ export class GitHubContainerRegistryIntegration extends Integration implements R
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof RequestError ? error.message : String(error);
|
||||
|
||||
localLogger.warn(`Failed to get releases for ${owner}\\${name} with GitHub Container Registry integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: errorMessage },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: errorMessage } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GithubIntegration" });
|
||||
@@ -43,38 +42,32 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with Github integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Github integration`, {
|
||||
identifier,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
const api = this.getApi();
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.rest.repos.listReleases({
|
||||
owner,
|
||||
repo: name,
|
||||
});
|
||||
const releasesResponse = await api.rest.repos.listReleases({ owner, repo: name });
|
||||
|
||||
if (releasesResponse.data.length === 0) {
|
||||
localLogger.warn(`No releases found, for ${repository.identifier} with Github integration`, {
|
||||
identifier: repository.identifier,
|
||||
localLogger.warn(`No releases found, for ${owner}/${name} with Github integration`, {
|
||||
identifier: `${owner}/${name}`,
|
||||
});
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
return { success: false, error: { code: "noMatchingVersion" } };
|
||||
}
|
||||
|
||||
const releasesProviderResponse = releasesResponse.data.reduce<ReleaseProviderResponse[]>((acc, release) => {
|
||||
@@ -90,20 +83,20 @@ export class GithubIntegration extends Integration implements ReleasesProviderIn
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
const details = await this.getDetailsAsync(api, owner, name);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof OctokitRequestError ? error.message : String(error);
|
||||
|
||||
localLogger.warn(`Failed to get releases for ${owner}\\${name} with Github integration`, {
|
||||
owner,
|
||||
name,
|
||||
error: errorMessage,
|
||||
});
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: errorMessage },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: errorMessage } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ import { getLatestRelease } from "../interfaces/releases-providers/releases-prov
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
|
||||
const localLogger = logger.child({ module: "GitlabIntegration" });
|
||||
@@ -40,25 +39,20 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const api = this.getApi();
|
||||
|
||||
const details = await this.getDetailsAsync(api, repository.identifier);
|
||||
|
||||
try {
|
||||
const releasesResponse = await api.ProjectReleases.all(repository.identifier, {
|
||||
const releasesResponse = await api.ProjectReleases.all(identifier, {
|
||||
perPage: 100,
|
||||
});
|
||||
|
||||
if (releasesResponse instanceof Error) {
|
||||
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, {
|
||||
identifier: repository.identifier,
|
||||
localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
|
||||
identifier,
|
||||
error: releasesResponse.message,
|
||||
});
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
return { success: false, error: { code: "noReleasesFound" } };
|
||||
}
|
||||
|
||||
const releasesProviderResponse = releasesResponse.reduce<ReleaseProviderResponse[]>((acc, release) => {
|
||||
@@ -76,17 +70,19 @@ export class GitlabIntegration extends Integration implements ReleasesProviderIn
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return getLatestRelease(releasesProviderResponse, repository, details);
|
||||
} catch (error) {
|
||||
localLogger.warn(`Failed to get releases for ${repository.identifier} with Gitlab integration`, {
|
||||
identifier: repository.identifier,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
const details = await this.getDetailsAsync(api, identifier);
|
||||
|
||||
return { success: true, data: { ...details, ...latestRelease } };
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
localLogger.warn(`Failed to get releases for ${identifier} with Gitlab integration`, {
|
||||
identifier,
|
||||
error: errorMessage,
|
||||
});
|
||||
return { success: false, error: { code: "unexpected", message: errorMessage } };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export type {
|
||||
TdarrStatistics,
|
||||
TdarrWorker,
|
||||
} from "./interfaces/media-transcoding/media-transcoding-types";
|
||||
export type { ReleasesResponse } from "./interfaces/releases-providers/releases-providers-types";
|
||||
export type { ReleasesRepository, ReleaseResponse } from "./interfaces/releases-providers/releases-providers-types";
|
||||
export type { Notification } from "./interfaces/notifications/notification-types";
|
||||
|
||||
// Schemas
|
||||
|
||||
@@ -1,47 +1,21 @@
|
||||
import type {
|
||||
DetailsProviderResponse,
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
} from "./releases-providers-types";
|
||||
import type { ReleaseProviderResponse, ReleaseResponse } from "./releases-providers-types";
|
||||
|
||||
export const getLatestRelease = (
|
||||
releases: ReleaseProviderResponse[],
|
||||
repository: ReleasesRepository,
|
||||
details?: DetailsProviderResponse,
|
||||
): ReleasesResponse => {
|
||||
versionRegex?: string,
|
||||
): ReleaseProviderResponse | null => {
|
||||
const validReleases = releases.filter((result) => {
|
||||
if (result.latestRelease) {
|
||||
return repository.versionRegex ? new RegExp(repository.versionRegex).test(result.latestRelease) : true;
|
||||
return versionRegex ? new RegExp(versionRegex).test(result.latestRelease) : true;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const latest =
|
||||
validReleases.length === 0
|
||||
? ({
|
||||
id: repository.id,
|
||||
error: { code: "noMatchingVersion" },
|
||||
} as ReleasesResponse)
|
||||
: validReleases.reduce(
|
||||
(latest, result) => {
|
||||
return {
|
||||
...details,
|
||||
...(result.latestReleaseAt > latest.latestReleaseAt ? result : latest),
|
||||
id: repository.id,
|
||||
};
|
||||
},
|
||||
{
|
||||
id: "",
|
||||
latestRelease: "",
|
||||
latestReleaseAt: new Date(0),
|
||||
},
|
||||
);
|
||||
|
||||
return latest;
|
||||
return validReleases.length === 0
|
||||
? null
|
||||
: validReleases.reduce((latest, current) => (current.latestReleaseAt > latest.latestReleaseAt ? current : latest));
|
||||
};
|
||||
|
||||
export interface ReleasesProviderIntegration {
|
||||
getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse>;
|
||||
getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import type { TranslationObject } from "@homarr/translation";
|
||||
|
||||
export interface ReleasesRepository extends Record<string, unknown> {
|
||||
id: string;
|
||||
identifier: string;
|
||||
versionRegex?: string;
|
||||
}
|
||||
|
||||
export interface DetailsProviderResponse {
|
||||
projectUrl?: string;
|
||||
projectDescription?: string;
|
||||
@@ -19,35 +25,10 @@ export interface ReleaseProviderResponse {
|
||||
isPreRelease?: boolean;
|
||||
}
|
||||
|
||||
export interface ReleasesRepository {
|
||||
id: string;
|
||||
identifier: string;
|
||||
versionRegex?: string;
|
||||
}
|
||||
|
||||
type ReleasesErrorKeys = keyof TranslationObject["widget"]["releases"]["error"]["messages"];
|
||||
|
||||
export interface ReleasesResponse {
|
||||
id: string;
|
||||
latestRelease?: string;
|
||||
latestReleaseAt?: Date;
|
||||
releaseUrl?: string;
|
||||
releaseDescription?: string;
|
||||
isPreRelease?: boolean;
|
||||
projectUrl?: string;
|
||||
projectDescription?: string;
|
||||
isFork?: boolean;
|
||||
isArchived?: boolean;
|
||||
createdAt?: Date;
|
||||
starsCount?: number;
|
||||
openIssues?: number;
|
||||
forksCount?: number;
|
||||
export type ReleaseData = DetailsProviderResponse & ReleaseProviderResponse;
|
||||
|
||||
error?:
|
||||
| {
|
||||
code: ReleasesErrorKeys;
|
||||
}
|
||||
| {
|
||||
message: string;
|
||||
};
|
||||
}
|
||||
export type ReleaseError = { code: ReleasesErrorKeys } | { code: "unexpected"; message: string };
|
||||
|
||||
export type ReleaseResponse = { success: true; data: ReleaseData } | { success: false; error: ReleaseError };
|
||||
|
||||
@@ -15,7 +15,7 @@ import { Integration } from "../base/integration";
|
||||
import type { TestingResult } from "../base/test-connection/test-connection-service";
|
||||
import type { IMediaServerIntegration } from "../interfaces/media-server/media-server-integration";
|
||||
import type { CurrentSessionsInput, StreamSession } from "../interfaces/media-server/media-server-types";
|
||||
import type { IMediaReleasesIntegration, MediaRelease } from "../types";
|
||||
import type { IMediaReleasesIntegration, MediaRelease, MediaType } from "../types";
|
||||
|
||||
@HandleIntegrationErrors([integrationAxiosHttpErrorHandler])
|
||||
export class JellyfinIntegration extends Integration implements IMediaServerIntegration, IMediaReleasesIntegration {
|
||||
@@ -122,7 +122,7 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
|
||||
return result.data.map((item) => ({
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: item.Id!,
|
||||
type: item.Type === "Movie" ? "movie" : item.Type === "Series" ? "tv" : "unknown",
|
||||
type: this.mapMediaReleaseType(item.Type),
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
title: item.Name!,
|
||||
subtitle: item.Taglines?.at(0),
|
||||
@@ -140,6 +140,27 @@ export class JellyfinIntegration extends Integration implements IMediaServerInte
|
||||
}));
|
||||
}
|
||||
|
||||
private mapMediaReleaseType(type: BaseItemKind | undefined): MediaType {
|
||||
switch (type) {
|
||||
case "Audio":
|
||||
case "AudioBook":
|
||||
case "MusicAlbum":
|
||||
return "music";
|
||||
case "Book":
|
||||
return "book";
|
||||
case "Episode":
|
||||
case "Series":
|
||||
case "Season":
|
||||
return "tv";
|
||||
case "Movie":
|
||||
return "movie";
|
||||
case "Video":
|
||||
return "video";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an ApiClient synchronously with an ApiKey or asynchronously
|
||||
* with a username and password.
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./linuxserverio-schemas";
|
||||
|
||||
const localLogger = logger.child({ module: "LinuxServerIOsIntegration" });
|
||||
@@ -24,56 +24,44 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = identifier.split("/");
|
||||
if (!owner || !name) {
|
||||
localLogger.warn(
|
||||
`Invalid identifier format. Expected 'owner/name', for ${repository.identifier} with LinuxServerIO integration`,
|
||||
{
|
||||
identifier: repository.identifier,
|
||||
},
|
||||
`Invalid identifier format. Expected 'owner/name', for ${identifier} with LinuxServerIO integration`,
|
||||
{ identifier },
|
||||
);
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "invalidIdentifier" },
|
||||
};
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string): Promise<ReleaseResponse> {
|
||||
const { name } = this.parseIdentifier(identifier) ?? {};
|
||||
if (!name) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url("/api/v1/images"));
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", 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 { success: false, error: { code: "unexpected", message: error.message } };
|
||||
}
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { code: "noReleasesFound" },
|
||||
};
|
||||
}
|
||||
const release = data.data.repositories.linuxserver.find((repo) => repo.name === name);
|
||||
if (!release) {
|
||||
localLogger.warn(`Repository ${name} not found on provider, with LinuxServerIO integration`, {
|
||||
name,
|
||||
});
|
||||
return { success: false, error: { code: "noMatchingVersion" } };
|
||||
}
|
||||
|
||||
return {
|
||||
id: repository.id,
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
latestRelease: release.version,
|
||||
latestReleaseAt: release.version_timestamp,
|
||||
releaseDescription: release.changelog?.shift()?.desc,
|
||||
@@ -82,7 +70,7 @@ export class LinuxServerIOIntegration extends Integration implements ReleasesPro
|
||||
isArchived: release.deprecated,
|
||||
createdAt: release.initial_date ? new Date(release.initial_date) : undefined,
|
||||
starsCount: release.stars,
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { TestConnectionError } from "../base/test-connection/test-connection-err
|
||||
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 { ReleasesRepository, ReleasesResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import type { ReleaseResponse } from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./npm-schemas";
|
||||
|
||||
export class NPMIntegration extends Integration implements ReleasesProviderIntegration {
|
||||
@@ -22,35 +22,35 @@ export class NPMIntegration extends Integration implements ReleasesProviderInteg
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(
|
||||
this.url(`/${encodeURIComponent(repository.identifier)}`),
|
||||
);
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
if (!identifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const releasesResponse = await fetchWithTrustedCertificatesAsync(this.url(`/${encodeURIComponent(identifier)}`));
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", message: releasesResponse.statusText } };
|
||||
}
|
||||
|
||||
const releasesResponseJson: unknown = await releasesResponse.json();
|
||||
const { data, success, error } = releasesResponseSchema.safeParse(releasesResponseJson);
|
||||
|
||||
if (!success) {
|
||||
return {
|
||||
id: repository.id,
|
||||
success: false,
|
||||
error: {
|
||||
code: "unexpected",
|
||||
message: releasesResponseJson ? JSON.stringify(releasesResponseJson, null, 2) : error.message,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const formattedReleases = data.time.map((tag) => ({
|
||||
...tag,
|
||||
releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(data.name)}/v/${encodeURIComponent(tag.latestRelease)}`,
|
||||
releaseDescription: data.versions[tag.latestRelease]?.description ?? "",
|
||||
}));
|
||||
return getLatestRelease(formattedReleases, repository);
|
||||
}
|
||||
|
||||
const formattedReleases = data.time.map((tag) => ({
|
||||
...tag,
|
||||
releaseUrl: `https://www.npmjs.com/package/${encodeURIComponent(data.name)}/v/${encodeURIComponent(tag.latestRelease)}`,
|
||||
releaseDescription: data.versions[tag.latestRelease]?.description ?? "",
|
||||
}));
|
||||
|
||||
const latestRelease = getLatestRelease(formattedReleases, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
return { success: true, data: latestRelease };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ import type { ReleasesProviderIntegration } from "../interfaces/releases-provide
|
||||
import { getLatestRelease } from "../interfaces/releases-providers/releases-providers-integration";
|
||||
import type {
|
||||
ReleaseProviderResponse,
|
||||
ReleasesRepository,
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
} from "../interfaces/releases-providers/releases-providers-types";
|
||||
import { releasesResponseSchema } from "./quay-schemas";
|
||||
|
||||
@@ -43,20 +42,22 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte
|
||||
};
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(repository: ReleasesRepository): Promise<ReleasesResponse> {
|
||||
const [owner, name] = repository.identifier.split("/");
|
||||
private parseIdentifier(identifier: string) {
|
||||
const [owner, name] = 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" },
|
||||
};
|
||||
localLogger.warn(`Invalid identifier format. Expected 'owner/name', for ${identifier} with Quay integration`, {
|
||||
identifier,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { owner, name };
|
||||
}
|
||||
|
||||
public async getLatestMatchingReleaseAsync(identifier: string, versionRegex?: string): Promise<ReleaseResponse> {
|
||||
const parsedIdentifier = this.parseIdentifier(identifier);
|
||||
if (!parsedIdentifier) return { success: false, error: { code: "invalidIdentifier" } };
|
||||
|
||||
const { owner, name } = parsedIdentifier;
|
||||
|
||||
const releasesResponse = await this.withHeadersAsync(async (headers) => {
|
||||
return await fetchWithTrustedCertificatesAsync(
|
||||
@@ -68,42 +69,29 @@ export class QuayIntegration extends Integration implements ReleasesProviderInte
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
if (!releasesResponse.ok) {
|
||||
return {
|
||||
id: repository.id,
|
||||
error: { message: releasesResponse.statusText },
|
||||
};
|
||||
return { success: false, error: { code: "unexpected", 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);
|
||||
return { success: false, error: { code: "unexpected", message: error.message } };
|
||||
}
|
||||
|
||||
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;
|
||||
}, []);
|
||||
|
||||
const latestRelease = getLatestRelease(releasesProviderResponse, versionRegex);
|
||||
if (!latestRelease) return { success: false, error: { code: "noMatchingVersion" } };
|
||||
|
||||
return { success: true, data: { projectDescription: data.description, ...latestRelease } };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/core": "workspace:^0.1.0",
|
||||
"superjson": "2.2.2",
|
||||
"superjson": "2.2.3",
|
||||
"winston": "3.18.3",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"next": "15.5.6",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"superjson": "2.2.2",
|
||||
"superjson": "2.2.3",
|
||||
"zod": "^4.1.12",
|
||||
"zod-form-data": "^3.0.1"
|
||||
},
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
"@homarr/db": "workspace:^",
|
||||
"@homarr/definitions": "workspace:^",
|
||||
"@homarr/log": "workspace:^",
|
||||
"ioredis": "5.8.1",
|
||||
"superjson": "2.2.2"
|
||||
"ioredis": "5.8.2",
|
||||
"superjson": "2.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"@homarr/redis": "workspace:^0.1.0",
|
||||
"dayjs": "^1.11.18",
|
||||
"octokit": "^5.0.4",
|
||||
"superjson": "2.2.2",
|
||||
"superjson": "2.2.3",
|
||||
"undici": "7.16.0",
|
||||
"zod": "^4.1.12"
|
||||
},
|
||||
|
||||
@@ -1,36 +1,19 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import type { IntegrationKindByCategory } from "@homarr/definitions";
|
||||
import { getIconUrl } from "@homarr/definitions";
|
||||
import type { ReleasesResponse } from "@homarr/integrations";
|
||||
import type { ReleaseResponse, ReleasesRepository } from "@homarr/integrations";
|
||||
import { createIntegrationAsync } from "@homarr/integrations";
|
||||
|
||||
import { createCachedIntegrationRequestHandler } from "./lib/cached-integration-request-handler";
|
||||
|
||||
export const releasesRequestHandler = createCachedIntegrationRequestHandler<
|
||||
ReleasesResponse,
|
||||
ReleaseResponse,
|
||||
IntegrationKindByCategory<"releasesProvider">,
|
||||
{
|
||||
id: string;
|
||||
identifier: string;
|
||||
versionRegex?: string;
|
||||
}
|
||||
ReleasesRepository
|
||||
>({
|
||||
async requestAsync(integration, input) {
|
||||
const integrationInstance = await createIntegrationAsync(integration);
|
||||
const response = await integrationInstance.getLatestMatchingReleaseAsync({
|
||||
id: input.id,
|
||||
identifier: input.identifier,
|
||||
versionRegex: input.versionRegex,
|
||||
});
|
||||
|
||||
return {
|
||||
...response,
|
||||
integration: {
|
||||
name: integration.name,
|
||||
iconUrl: getIconUrl(integration.kind),
|
||||
},
|
||||
};
|
||||
requestAsync: async (integration, input) => {
|
||||
const instance = await createIntegrationAsync(integration);
|
||||
return instance.getLatestMatchingReleaseAsync(input.identifier, input.versionRegex);
|
||||
},
|
||||
cacheDuration: dayjs.duration(5, "minutes"),
|
||||
queryKey: "repositoriesReleases",
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"deepmerge": "4.3.1",
|
||||
"mantine-react-table": "2.0.0-beta.9",
|
||||
"next": "15.5.6",
|
||||
"next-intl": "4.3.12",
|
||||
"next-intl": "4.4.0",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0"
|
||||
},
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Creació fallida",
|
||||
"message": "No s'ha pogut crear la integració"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "No s'han pogut aplicar els canvis",
|
||||
"message": "No s'ha pogut desar la integració"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Crea un motor de cerca",
|
||||
"description": "La integració \"{kind}\" es pot utilitzar amb els motors de cerca. Seleccioneu aquesta opció per configurar el motor de cerca automàticament."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "",
|
||||
"apply": "",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "",
|
||||
"createAnother": "",
|
||||
"edit": "",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "创建失败",
|
||||
"message": "无法创建组件"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "无法应用更改",
|
||||
"message": "无法保存此集成"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "创建搜索引擎",
|
||||
"description": "集成“{kind}”可以与搜索引擎一起使用。勾选此项可自动配置搜索引擎。"
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "创建应用",
|
||||
"description": "创建一个具有与集成相同名称和图标的应用程序。 留空下面的输入字段使用集成URL创建应用程序。"
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "添加",
|
||||
"apply": "应用",
|
||||
"backToOverview": "返回概览",
|
||||
"change": "",
|
||||
"create": "创建",
|
||||
"createAnother": "创建并重新开始",
|
||||
"edit": "编辑",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "crwdns346:0crwdne346:0",
|
||||
"message": "crwdns348:0crwdne348:0"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "crwdns3786:0crwdne3786:0",
|
||||
"label": "crwdns3788:0crwdne3788:0"
|
||||
},
|
||||
"new": {
|
||||
"title": "crwdns3790:0crwdne3790:0",
|
||||
"url": {
|
||||
"label": "crwdns3792:0crwdne3792:0",
|
||||
"description": "crwdns3794:0crwdne3794:0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "crwdns356:0crwdne356:0",
|
||||
"message": "crwdns358:0crwdne358:0"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "crwdns3796:0crwdne3796:0",
|
||||
"remove": "crwdns3798:0crwdne3798:0",
|
||||
"select": "crwdns3800:0crwdne3800:0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "crwdns2512:0crwdne2512:0",
|
||||
"description": "crwdns2514:0{kind}crwdne2514:0"
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": "crwdns3802:0crwdne3802:0"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "crwdns3050:0crwdne3050:0",
|
||||
"description": "crwdns3052:0crwdne3052:0"
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "crwdns512:0crwdne512:0",
|
||||
"apply": "crwdns514:0crwdne514:0",
|
||||
"backToOverview": "crwdns516:0crwdne516:0",
|
||||
"change": "crwdns3804:0crwdne3804:0",
|
||||
"create": "crwdns518:0crwdne518:0",
|
||||
"createAnother": "crwdns2720:0crwdne2720:0",
|
||||
"edit": "crwdns520:0crwdne520:0",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Vytvoření se nezdařilo",
|
||||
"message": "Integraci se nepodařilo vytvořit"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Nepodařilo se uložit změny",
|
||||
"message": "Integraci se nepodařilo uložit"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Přidat",
|
||||
"apply": "Použít",
|
||||
"backToOverview": "Zpět na přehled",
|
||||
"change": "",
|
||||
"create": "Vytvořit",
|
||||
"createAnother": "",
|
||||
"edit": "Upravit",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Oprettelse mislykkedes",
|
||||
"message": "Integration kunne ikke oprettes"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "Eksisterende",
|
||||
"label": "Vælg eksisterende app"
|
||||
},
|
||||
"new": {
|
||||
"title": "Ny",
|
||||
"url": {
|
||||
"label": "App url",
|
||||
"description": "Webadressen som app'en vil åbne, når den tilgås fra betjeningspanelet"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Kan ikke anvende ændringer",
|
||||
"message": "Integration kunne ikke gemmes"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "Link en app",
|
||||
"remove": "Fjern link",
|
||||
"select": "Vælg en app der skal linkes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Opret Søgemaskine",
|
||||
"description": "Integration \"{kind}\" kan bruges med søgemaskinerne. Markér dette for automatisk at konfigurere søgemaskinen."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": "Linket App"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Opret app",
|
||||
"description": "Opret en app med samme navn og ikon som integrationen. Lad input-feltet være tomt for at oprette app'en med integrations-URL'en."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Tilføj",
|
||||
"apply": "Anvend",
|
||||
"backToOverview": "Tilbage til oversigt",
|
||||
"change": "Skift",
|
||||
"create": "Opret",
|
||||
"createAnother": "Opret og start forfra",
|
||||
"edit": "Rediger",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Erstellig isch fählgschlage",
|
||||
"message": "D Integration het nid chönne erstäut werde"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Änderige chöi nöd aawendet werde",
|
||||
"message": "D Integration het nöd chönne gspichert werde"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Suechmaschine erstelle",
|
||||
"description": "Integration '{kind}' cha als Suechmaschine verwendet werde. Wähl das us, um se outomatisch als Suechmaschine z konfiguriere."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "App ersteue",
|
||||
"description": "Mach en App mit de gleiche Name und dem gleiche Icon wie d'Integration. Lass d'Eingabefeld unger im Fall leer, um d'App mit em Integrations-URL z'schaffe."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Hinzufügen",
|
||||
"apply": "Übernehmen",
|
||||
"backToOverview": "Zurück zur Übersicht",
|
||||
"change": "",
|
||||
"create": "Erstellen",
|
||||
"createAnother": "Erstellen und neu starten",
|
||||
"edit": "Bearbeiten",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Erstellung fehlgeschlagen",
|
||||
"message": "Die Integration konnte nicht erstellt werden"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Änderungen konnten nicht angewendet werden",
|
||||
"message": "Die Integration konnte nicht gespeichert werden"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Suchmaschinen anlegen",
|
||||
"description": "Integration \"{kind}\" kann mit den Suchmaschinen verwendet werden. Wählen Sie dies, um die Suchmaschine automatisch zu konfigurieren."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "App erstellen",
|
||||
"description": "Eine App mit dem gleichen Namen und Symbol wie die Integration erstellen. Lassen Sie das Eingabefeld unter leer, um die App mit der Integrations URL zu erstellen."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Hinzufügen",
|
||||
"apply": "Übernehmen",
|
||||
"backToOverview": "Zurück zur Übersicht",
|
||||
"change": "",
|
||||
"create": "Erstellen",
|
||||
"createAnother": "Erstellen und neu starten",
|
||||
"edit": "Bearbeiten",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Προσθήκη",
|
||||
"apply": "Εφαρμογή",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "Δημιουργία",
|
||||
"createAnother": "",
|
||||
"edit": "Επεξεργασία",
|
||||
|
||||
@@ -612,17 +612,17 @@
|
||||
"select": {
|
||||
"label": "Select an application",
|
||||
"notFound": "No application found",
|
||||
"search": "",
|
||||
"noResults": "",
|
||||
"action": "",
|
||||
"title": ""
|
||||
"search": "Search for an app",
|
||||
"noResults": "No results",
|
||||
"action": "Select {app}",
|
||||
"title": "Select an app to add to this board"
|
||||
},
|
||||
"create": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"action": ""
|
||||
"title": "Create new app",
|
||||
"description": "Create a new app ",
|
||||
"action": "Open app creation"
|
||||
},
|
||||
"add": ""
|
||||
"add": "Add an app"
|
||||
}
|
||||
},
|
||||
"integration": {
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Creation failed",
|
||||
"message": "The integration was unable to be created"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "Existing",
|
||||
"label": "Select existing app"
|
||||
},
|
||||
"new": {
|
||||
"title": "New",
|
||||
"url": {
|
||||
"label": "App URL",
|
||||
"description": "The URL the app will open when accessed from the dashboard"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Changes have not been applied",
|
||||
"message": "The integration was unable to be saved"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "Link an app",
|
||||
"remove": "Unlink",
|
||||
"select": "Select an app to link"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Create a search engine",
|
||||
"description": "Integration, {kind} can be used with the search engine. Please check this to automatically configure the search engine."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": "Linked App"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Create application",
|
||||
"description": "Create an application with the same information as the integration. Please leave the input field below empty to create the application with the integration URL."
|
||||
@@ -705,30 +730,30 @@
|
||||
"error": {
|
||||
"common": {
|
||||
"cause": {
|
||||
"title": ""
|
||||
"title": "Cause with more details"
|
||||
}
|
||||
},
|
||||
"unknown": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Unknown error",
|
||||
"description": "An unknown error occurred, open the cause below to see more details"
|
||||
},
|
||||
"parse": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Parse error",
|
||||
"description": "The response could not be parsed. Please verify that the URL is pointing to the base URL of the service."
|
||||
},
|
||||
"authorization": {
|
||||
"title": "",
|
||||
"description": ""
|
||||
"title": "Authorisation error",
|
||||
"description": "The request was not authorised. Please verify that the credentials are correct and you have them configured with enough permissions."
|
||||
},
|
||||
"statusCode": {
|
||||
"title": "",
|
||||
"description": "",
|
||||
"otherDescription": "",
|
||||
"title": "Response error",
|
||||
"description": "Received unexpected {statusCode} ({reason}) response from <url></url>. Please verify that the URL is pointing to the base URL of the integration.",
|
||||
"otherDescription": "Received unexpected {statusCode} response from <url></url>. Please verify that the URL is pointing to the base URL of the integration.",
|
||||
"reason": {
|
||||
"badRequest": "",
|
||||
"notFound": "",
|
||||
"tooManyRequests": "",
|
||||
"internalServerError": "",
|
||||
"badRequest": "Bad request",
|
||||
"notFound": "Not found",
|
||||
"tooManyRequests": "Too many requests",
|
||||
"internalServerError": "Internal server error",
|
||||
"serviceUnavailable": "",
|
||||
"gatewayTimeout": ""
|
||||
}
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Add",
|
||||
"apply": "Apply",
|
||||
"backToOverview": "Back to overview",
|
||||
"change": "",
|
||||
"create": "Create",
|
||||
"createAnother": "Create and start over",
|
||||
"edit": "Edit",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "No se ha podido crear",
|
||||
"message": "La integración no pudo ser creada"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "Existente",
|
||||
"label": "Seleccionar aplicación existente"
|
||||
},
|
||||
"new": {
|
||||
"title": "Nueva",
|
||||
"url": {
|
||||
"label": "URL de aplicación",
|
||||
"description": "La URL que abrirá la aplicación cuando se acceda desde el panel de control"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "No se pudieron aplicar los cambios",
|
||||
"message": "La integración no pudo ser creada"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "Vincular una aplicación",
|
||||
"remove": "Desvincular",
|
||||
"select": "Selecciona una aplicación para vincular"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Crear motor de búsqueda",
|
||||
"description": "La integración \"{kind}\" se puede utilizar con los motores de búsqueda. Marque esta opción para configurar automáticamente el motor de búsqueda."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": "Aplicación vinculada"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Crear aplicación",
|
||||
"description": "Crea una aplicación con el mismo nombre e icono que la integración. Deja vacío el campo de entrada de abajo para crear la aplicación con la URL de la integración."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Añadir",
|
||||
"apply": "Aplicar",
|
||||
"backToOverview": "Volver a la vista general",
|
||||
"change": "Modificar",
|
||||
"create": "Crear",
|
||||
"createAnother": "Crear e iniciar de nuevo",
|
||||
"edit": "Editar",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "",
|
||||
"apply": "",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "",
|
||||
"createAnother": "",
|
||||
"edit": "",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Échec de la création",
|
||||
"message": "L'intégration n'a pas pu être créée"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Impossible d'appliquer les modifications",
|
||||
"message": "L'intégration n'a pas pu être enregistrée"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Créer un moteur de recherche",
|
||||
"description": "L'intégration \"{kind}\" peut être utilisée avec les moteurs de recherche. Cochez ceci pour configurer automatiquement le moteur de recherche."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Créer une application",
|
||||
"description": "Créer une application avec le même nom et l'icône que l'intégration. Laissez le champ de saisie ci-dessous vide pour créer l'application avec l'URL d'intégration."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Ajouter",
|
||||
"apply": "Appliquer",
|
||||
"backToOverview": "Retourner à l'aperçu",
|
||||
"change": "",
|
||||
"create": "Créer",
|
||||
"createAnother": "Créer et recommencer",
|
||||
"edit": "Modifier",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "היצירה נכשלה",
|
||||
"message": "לא ניתן ליצור את האינטגרציה"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "קיים",
|
||||
"label": "בחירת אפליקציה קיימת"
|
||||
},
|
||||
"new": {
|
||||
"title": "חדש",
|
||||
"url": {
|
||||
"label": "כתובת אתר של אפליקציה",
|
||||
"description": "כתובת האתר שהאפליקציה תפתח בעת גישה אליה מלוח המחוונים"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "לא ניתן להחיל שינויים",
|
||||
"message": "לא ניתן היה לשמור את האינטגרציה"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "קישור אפליקציה",
|
||||
"remove": "בטל קישור",
|
||||
"select": "בחר אפליקציה לקישור"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "יצירת מנוע חיפוש",
|
||||
"description": "אינטגרציה {kind} יכולה לשמש עם מנועי חיפוש. סמן עבור הגדרה אוטומטית של מנוע חיפוש."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": "אפליקציה מקושרת"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "יצירת אפליקציה",
|
||||
"description": "יצירת אפליקציה עם אותו שם וסמל כמו האינטגרציה. יש להשאיר את שדה הקלט למטה ריק כדי ליצור את האפליקציה עם כתובת האתר לאינטגרציה."
|
||||
@@ -946,8 +971,8 @@
|
||||
"newLabel": "נושא חדש"
|
||||
},
|
||||
"url": {
|
||||
"label": "",
|
||||
"newLabel": ""
|
||||
"label": "כתובת אתר",
|
||||
"newLabel": "כתובת אתר חדשה"
|
||||
},
|
||||
"opnsenseApiKey": {
|
||||
"label": "מפתח API (מפתח)",
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "הוסף",
|
||||
"apply": "החל",
|
||||
"backToOverview": "חזרה לסקירה כללית",
|
||||
"change": "לשנות",
|
||||
"create": "צור",
|
||||
"createAnother": "צור והתחל מחדש",
|
||||
"edit": "עריכה",
|
||||
@@ -1148,8 +1174,8 @@
|
||||
},
|
||||
"unit": {
|
||||
"speed": {
|
||||
"kilometersPerHour": "",
|
||||
"milesPerHour": ""
|
||||
"kilometersPerHour": "קמ\"ש",
|
||||
"milesPerHour": "מייל/שעה"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -1164,7 +1190,7 @@
|
||||
"label": "כותרת"
|
||||
},
|
||||
"customCssClasses": {
|
||||
"label": ""
|
||||
"label": "מחלקות עיצוב מותאמות אישית"
|
||||
},
|
||||
"borderColor": {
|
||||
"label": "צבע מסגרת"
|
||||
@@ -1717,7 +1743,7 @@
|
||||
"name": "לוח שנה",
|
||||
"description": "הצג אירועים מהאינטגרציות שלך בתצוגת לוח שנה בתוך פרק זמן יחסי מסוים",
|
||||
"duration": {
|
||||
"allDay": ""
|
||||
"allDay": "כל היום"
|
||||
},
|
||||
"option": {
|
||||
"releaseType": {
|
||||
@@ -1754,7 +1780,7 @@
|
||||
"description": "רק במזג אוויר נוכחי"
|
||||
},
|
||||
"useImperialSpeed": {
|
||||
"label": ""
|
||||
"label": "השתמש במייל לשעה עבור מהירות הרוח"
|
||||
},
|
||||
"location": {
|
||||
"label": "מיקום מזג האוויר"
|
||||
@@ -1994,21 +2020,21 @@
|
||||
"name": "שם",
|
||||
"id": "מזהה",
|
||||
"metadata": {
|
||||
"title": "",
|
||||
"title": "סטטיסטיקות לחנונים",
|
||||
"video": {
|
||||
"title": "",
|
||||
"resolution": ""
|
||||
"title": "וידאו",
|
||||
"resolution": "רזולוציה"
|
||||
},
|
||||
"audio": {
|
||||
"title": "",
|
||||
"channelCount": "",
|
||||
"codec": ""
|
||||
"title": "אודיו",
|
||||
"channelCount": "ערוצי אודיו",
|
||||
"codec": "מקודדי אודיו"
|
||||
},
|
||||
"transcoding": {
|
||||
"title": "",
|
||||
"container": "",
|
||||
"resolution": "",
|
||||
"target": ""
|
||||
"title": "המרה",
|
||||
"container": "מיכל",
|
||||
"resolution": "רזולוציה",
|
||||
"target": "קידוד יעד"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2537,7 +2563,7 @@
|
||||
"description": "שימוש במעבד, זיכרון, דיסק וחומרה אחרת של המערכת שלך",
|
||||
"option": {
|
||||
"hasShadow": {
|
||||
"label": ""
|
||||
"label": "הפעלת הצללת תרשים"
|
||||
},
|
||||
"visibleCharts": {
|
||||
"label": "תרשימים גלויים",
|
||||
@@ -2549,12 +2575,12 @@
|
||||
}
|
||||
},
|
||||
"labelDisplayMode": {
|
||||
"label": "",
|
||||
"label": "מצב תצוגת תוויות",
|
||||
"option": {
|
||||
"textWithIcon": "",
|
||||
"text": "",
|
||||
"icon": "",
|
||||
"hidden": ""
|
||||
"textWithIcon": "הצג טקסט עם סמל",
|
||||
"text": "הצג רק טקסט",
|
||||
"icon": "הצג רק אייקון",
|
||||
"hidden": "הסתר תוויות"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2987,8 +3013,8 @@
|
||||
"integration": "אינטגרציות",
|
||||
"app": "אפליקציות",
|
||||
"group": "קבוצות",
|
||||
"searchEngine": "",
|
||||
"media": ""
|
||||
"searchEngine": "מנועי חיפוש",
|
||||
"media": "מדיה"
|
||||
},
|
||||
"statisticLabel": {
|
||||
"boards": "לוחות",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Dodaj",
|
||||
"apply": "",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "Stvoriti",
|
||||
"createAnother": "",
|
||||
"edit": "Uredi",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "A létrehozás nem sikerült",
|
||||
"message": "Az integráció nem hozható létre"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Nem sikerült alkalmazni a változtatásokat",
|
||||
"message": "Az integrációt nem sikerült menteni"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Hozzáadás",
|
||||
"apply": "Alkalmaz",
|
||||
"backToOverview": "Vissza az áttekintéshez",
|
||||
"change": "",
|
||||
"create": "Létrehozás",
|
||||
"createAnother": "",
|
||||
"edit": "Szerkesztés",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Creazione fallita",
|
||||
"message": "Non è stato possibile creare l'integrazione"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "Esistente",
|
||||
"label": "Seleziona app esistente"
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "Url app",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Impossibile applicare le modifiche",
|
||||
"message": "Non è stato possibile salvare l'integrazione"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Crea Motore Di Ricerca",
|
||||
"description": "Integrazione \"{kind}\" può essere utilizzato con i motori di ricerca. Selezionare questa opzione per configurare automaticamente il motore di ricerca."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Crea app",
|
||||
"description": "Crea un'app con lo stesso nome ed icona dell'integrazione. Lasciare vuoto il campo di input qui sotto per creare l'applicazione con l'URL di integrazione."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Aggiungi",
|
||||
"apply": "Applica",
|
||||
"backToOverview": "Torna alla panoramica",
|
||||
"change": "",
|
||||
"create": "Crea",
|
||||
"createAnother": "",
|
||||
"edit": "Modifica",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "作成失敗",
|
||||
"message": "連携機能を作成できません"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "変更の適用失敗",
|
||||
"message": "連携機能を保存できません"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "検索エンジンを作成",
|
||||
"description": "連携機能 \"{kind}\" は、検索エンジンで使用できます。検索エンジンを自動的に設定するには、これにチェックを入れてください。"
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "アプリの作成",
|
||||
"description": "連携機能と同じ名前とアイコンを持つアプリを作成します。 アプリケーションを作成するには、以下の入力フィールドを空のままにしてください。"
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "追加",
|
||||
"apply": "適用",
|
||||
"backToOverview": "概要に戻る",
|
||||
"change": "",
|
||||
"create": "作成",
|
||||
"createAnother": "作成・新規入力",
|
||||
"edit": "編集",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "추가",
|
||||
"apply": "",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "만들기",
|
||||
"createAnother": "",
|
||||
"edit": "수정",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "",
|
||||
"apply": "",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "Sukurti",
|
||||
"createAnother": "",
|
||||
"edit": "",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Pievienot",
|
||||
"apply": "Lietot",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "Izveidot",
|
||||
"createAnother": "",
|
||||
"edit": "Rediģēt",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Aanmaken mislukt",
|
||||
"message": "De integratie kon niet worden aangemaakt"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Kan wijzigingen niet toepassen",
|
||||
"message": "De integratie kon niet worden opgeslagen"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Zoekmachine aanmaken",
|
||||
"description": "Integratie \"{kind}\" kan worden gebruikt met de zoekmachines. Controleer dit om automatisch de zoekmachine te configureren."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "App aanmaken",
|
||||
"description": "Maak een app n met dezelfde naam en icoon als de integratie. Laat het invoerveld hieronder leeg om de app te maken met de integratie-URL."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Toevoegen",
|
||||
"apply": "Toepassen",
|
||||
"backToOverview": "Terug naar overzicht",
|
||||
"change": "",
|
||||
"create": "Aanmaken",
|
||||
"createAnother": "Aanmaken en opnieuw beginnen",
|
||||
"edit": "Bewerken",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Opprettelse mislyktes",
|
||||
"message": "Integrasjonen kunne ikke opprettes"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Kunne ikke fullføre endringer",
|
||||
"message": "Integrasjonen kunne ikke lagres"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Opprett søkemotor",
|
||||
"description": "Integrasjon \"{kind}\" kan brukes med søkemotorene. Huk av her for å automatisk konfigurere søkemotor."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Legg til",
|
||||
"apply": "Bruk",
|
||||
"backToOverview": "Tilbake til oversikt",
|
||||
"change": "",
|
||||
"create": "Opprett",
|
||||
"createAnother": "Opprett og start på nytt",
|
||||
"edit": "Rediger",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Nie udało się utworzyć",
|
||||
"message": "Integracja nie może zostać utworzona"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "Istniejąca",
|
||||
"label": "Wybierz istniejącą aplikację"
|
||||
},
|
||||
"new": {
|
||||
"title": "Nowy",
|
||||
"url": {
|
||||
"label": "Adres URL aplikacji",
|
||||
"description": "Adres URL, który zostanie otwarty po kliknięciu aplikacji na pulpicie"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Nie można wprowadzić zmian",
|
||||
"message": "Integracja nie mogła zostać zapisana"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "Połącz aplikację",
|
||||
"remove": "Odłącz",
|
||||
"select": "Wybierz aplikację do połączenia"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Utwórz wyszukiwarkę",
|
||||
"description": "Integracja \"{kind}\" może być używana z wyszukiwarkami. Zaznacz to, aby automatycznie skonfigurować wyszukiwarkę."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": "Połączona aplikacja"
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Stwórz aplikację",
|
||||
"description": "Utwórz aplikację o tej samej nazwie i ikonie, co integracja. Poniższe pole pozostaw puste, aby utworzyć aplikację z URL integracji."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Dodaj",
|
||||
"apply": "Zastosuj",
|
||||
"backToOverview": "Powrót do widoku ogólnego",
|
||||
"change": "Zmień",
|
||||
"create": "Utwórz",
|
||||
"createAnother": "Utwórz i zacznij od nowa",
|
||||
"edit": "Edytuj",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Adicionar",
|
||||
"apply": "Aplicar",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "Criar",
|
||||
"createAnother": "",
|
||||
"edit": "Editar",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Adaugă",
|
||||
"apply": "Aplică",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "Creează",
|
||||
"createAnother": "",
|
||||
"edit": "Editare",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Не удалось создать",
|
||||
"message": "Не удалось создать интеграцию"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Не удалось сохранить",
|
||||
"message": "Не удалось сохранить интеграцию"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Создать поисковую систему",
|
||||
"description": "Интеграцию \"{kind}\" можно использовать с поисковыми системами. Включите эту опцию для автоматической настройки поисковой системы."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Добавить",
|
||||
"apply": "Применить",
|
||||
"backToOverview": "Вернуться к обзору",
|
||||
"change": "",
|
||||
"create": "Создать",
|
||||
"createAnother": "Создать и начать заново",
|
||||
"edit": "Редактировать",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Vytvorenie zlyhalo",
|
||||
"message": "Integráciu nebolo možné vytvoriť"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Nie je možné použiť zmeny",
|
||||
"message": "Integráciu sa nepodarilo uložiť"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Vytvorte vyhľadávače",
|
||||
"description": "Integrácia \"{kind}\" môže byť použitá s vyhľadávačmi. Toto začiarknite, ak chcete automaticky nakonfigurovať vyhľadávací nástroj."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Vytvoriť aplikáciu",
|
||||
"description": "Vytvorte aplikáciu s rovnakým názvom a ikonou ako integrácia. Ak chcete vytvoriť aplikáciu s integračnou webovou adresou, ponechajte vstupné pole nižšie prázdne."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Pridať",
|
||||
"apply": "Použiť",
|
||||
"backToOverview": "Späť na prehľad",
|
||||
"change": "",
|
||||
"create": "Vytvoriť",
|
||||
"createAnother": "Vytvorte a začnite odznova",
|
||||
"edit": "Upraviť",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Dodaj",
|
||||
"apply": "Uporabi",
|
||||
"backToOverview": "",
|
||||
"change": "",
|
||||
"create": "Ustvarite spletno stran",
|
||||
"createAnother": "",
|
||||
"edit": "Uredi",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Lägg till",
|
||||
"apply": "Använd",
|
||||
"backToOverview": "Tillbaka till översikten",
|
||||
"change": "",
|
||||
"create": "Addera",
|
||||
"createAnother": "Addera och börja om",
|
||||
"edit": "Redigera",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Oluşturma başarısız oldu",
|
||||
"message": "Entegrasyon oluşturulamadı"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Değişiklikler uygulanamıyor",
|
||||
"message": "Entegrasyon kaydedilemedi"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Arama Motoru Oluştur",
|
||||
"description": "\"{kind}\" entegrasyonu arama motorlarıyla kullanılabilir. Arama motorunu otomatik olarak yapılandırmak için bunu işaretleyin."
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Uygulama oluştur",
|
||||
"description": "Entegrasyon ile aynı adı ve simgeyi taşıyan bir uygulama oluşturun. Uygulamayı entegrasyon URL'si ile oluşturmak için aşağıdaki alanını boş bırakın."
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Ekle",
|
||||
"apply": "Uygula",
|
||||
"backToOverview": "Genel bakışa dön",
|
||||
"change": "",
|
||||
"create": "Oluştur",
|
||||
"createAnother": "Kaydet ve Yeni Oluştur",
|
||||
"edit": "Düzenle",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "Не вдалося створити",
|
||||
"message": "Не вдалося створити інтеграцію"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "Не вдалося застосувати зміни",
|
||||
"message": "Не вдалося зберегти інтеграцію"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Створення пошукових систем",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Додати",
|
||||
"apply": "Застосувати",
|
||||
"backToOverview": "Назад до огляду",
|
||||
"change": "",
|
||||
"create": "Створити",
|
||||
"createAnother": "",
|
||||
"edit": "Редагувати",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "",
|
||||
"message": ""
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "Tạo công cụ tìm kiếm",
|
||||
"description": ""
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "Tạo ứng dụng",
|
||||
"description": ""
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "Thêm",
|
||||
"apply": "Áp dụng",
|
||||
"backToOverview": "Trở về mục tổng quan",
|
||||
"change": "",
|
||||
"create": "Tạo nên",
|
||||
"createAnother": "",
|
||||
"edit": "Sửa",
|
||||
|
||||
@@ -645,6 +645,21 @@
|
||||
"title": "創建失敗",
|
||||
"message": "此集成無法被創建"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"option": {
|
||||
"existing": {
|
||||
"title": "",
|
||||
"label": ""
|
||||
},
|
||||
"new": {
|
||||
"title": "",
|
||||
"url": {
|
||||
"label": "",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"edit": {
|
||||
@@ -658,6 +673,13 @@
|
||||
"title": "無法應用變更",
|
||||
"message": "集成無法被儲存"
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"action": {
|
||||
"add": "",
|
||||
"remove": "",
|
||||
"select": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
@@ -686,6 +708,9 @@
|
||||
"label": "創建搜尋引擎",
|
||||
"description": "集成 {kind} 可以與搜尋引擎共同使用,勾選此選項可自動設定搜尋引擎"
|
||||
},
|
||||
"app": {
|
||||
"sectionTitle": ""
|
||||
},
|
||||
"createApp": {
|
||||
"label": "創建應用程式",
|
||||
"description": "建立一個與集成相同名稱和圖示的應用程式,將下方輸入欄位留空,以使用集成網址創建應用程式"
|
||||
@@ -1026,6 +1051,7 @@
|
||||
"add": "新增",
|
||||
"apply": "應用",
|
||||
"backToOverview": "返回總覽",
|
||||
"change": "",
|
||||
"create": "創建",
|
||||
"createAnother": "創建並重新開始",
|
||||
"edit": "編輯",
|
||||
|
||||
@@ -52,22 +52,22 @@
|
||||
"@mantine/core": "^8.3.5",
|
||||
"@mantine/hooks": "^8.3.5",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"@tiptap/extension-color": "2.26.3",
|
||||
"@tiptap/extension-highlight": "2.26.3",
|
||||
"@tiptap/extension-image": "2.26.3",
|
||||
"@tiptap/extension-link": "^2.26.3",
|
||||
"@tiptap/extension-placeholder": "^2.26.3",
|
||||
"@tiptap/extension-table": "2.26.3",
|
||||
"@tiptap/extension-table-cell": "2.26.3",
|
||||
"@tiptap/extension-table-header": "2.26.3",
|
||||
"@tiptap/extension-table-row": "2.26.3",
|
||||
"@tiptap/extension-task-item": "2.26.3",
|
||||
"@tiptap/extension-task-list": "2.26.3",
|
||||
"@tiptap/extension-text-align": "2.26.3",
|
||||
"@tiptap/extension-text-style": "2.26.3",
|
||||
"@tiptap/extension-underline": "2.26.3",
|
||||
"@tiptap/react": "^2.26.3",
|
||||
"@tiptap/starter-kit": "^2.26.3",
|
||||
"@tiptap/extension-color": "2.26.4",
|
||||
"@tiptap/extension-highlight": "2.26.4",
|
||||
"@tiptap/extension-image": "2.26.4",
|
||||
"@tiptap/extension-link": "^2.26.4",
|
||||
"@tiptap/extension-placeholder": "^2.26.4",
|
||||
"@tiptap/extension-table": "2.26.4",
|
||||
"@tiptap/extension-table-cell": "2.26.4",
|
||||
"@tiptap/extension-table-header": "2.26.4",
|
||||
"@tiptap/extension-table-row": "2.26.4",
|
||||
"@tiptap/extension-task-item": "2.26.4",
|
||||
"@tiptap/extension-task-list": "2.26.4",
|
||||
"@tiptap/extension-text-align": "2.26.4",
|
||||
"@tiptap/extension-text-style": "2.26.4",
|
||||
"@tiptap/extension-underline": "2.26.4",
|
||||
"@tiptap/react": "^2.26.4",
|
||||
"@tiptap/starter-kit": "^2.26.4",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.18",
|
||||
"mantine-form-zod-resolver": "^1.3.0",
|
||||
|
||||
@@ -21,6 +21,7 @@ import ReactMarkdown from "react-markdown";
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useRequiredBoard } from "@homarr/boards/context";
|
||||
import { isDateWithin, isNullOrWhitespace, splitToChunksWithNItems } from "@homarr/common";
|
||||
import { getIconUrl } from "@homarr/definitions";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { MaskedOrNormalImage } from "@homarr/ui";
|
||||
|
||||
@@ -96,55 +97,33 @@ export default function ReleasesWidget({ options }: WidgetComponentProps<"releas
|
||||
const repositories = useMemo(() => {
|
||||
const formattedResults = options.repositories
|
||||
.map((repository) => {
|
||||
if (repository.providerIntegrationId === undefined) {
|
||||
return {
|
||||
...repository,
|
||||
isNewRelease: false,
|
||||
isStaleRelease: false,
|
||||
latestReleaseAt: undefined,
|
||||
error: {
|
||||
code: "noProviderSeleceted",
|
||||
},
|
||||
};
|
||||
}
|
||||
if (!repository.providerIntegrationId) return { ...repository, error: { code: "noProviderSeleceted" } };
|
||||
|
||||
const response = results.flat().find(({ data }) => data.id === repository.id)?.data;
|
||||
const repositoryResult = results.flat().find(({ id }) => id === repository.id);
|
||||
if (!repositoryResult) return { ...repository, error: { code: "noProviderResponse" } };
|
||||
if (!repositoryResult.success) return { ...repository, error: repositoryResult.error };
|
||||
|
||||
if (response === undefined)
|
||||
return {
|
||||
...repository,
|
||||
isNewRelease: false,
|
||||
isStaleRelease: false,
|
||||
latestReleaseAt: undefined,
|
||||
error: {
|
||||
code: "noProviderResponse",
|
||||
},
|
||||
};
|
||||
const { data: release, integration } = repositoryResult;
|
||||
|
||||
const isReleaseWithin = (relativeDate: string) =>
|
||||
Boolean(relativeDate) && isDateWithin(release.latestReleaseAt, relativeDate);
|
||||
|
||||
return {
|
||||
...repository,
|
||||
...response,
|
||||
isNewRelease:
|
||||
relativeDateOptions.newReleaseWithin !== "" && response.latestReleaseAt
|
||||
? isDateWithin(response.latestReleaseAt, relativeDateOptions.newReleaseWithin)
|
||||
: false,
|
||||
isStaleRelease:
|
||||
relativeDateOptions.staleReleaseWithin !== "" && response.latestReleaseAt
|
||||
? !isDateWithin(response.latestReleaseAt, relativeDateOptions.staleReleaseWithin)
|
||||
: false,
|
||||
viewed: releasesViewedList[repository.id] === response.latestRelease,
|
||||
...release,
|
||||
integration: { name: integration.name, iconUrl: getIconUrl(integration.kind) },
|
||||
isNewRelease: isReleaseWithin(relativeDateOptions.newReleaseWithin),
|
||||
isStaleRelease: !isReleaseWithin(relativeDateOptions.staleReleaseWithin),
|
||||
viewed: releasesViewedList[repository.id] === release.latestRelease,
|
||||
};
|
||||
})
|
||||
.filter(
|
||||
(repository) =>
|
||||
repository.error !== undefined ||
|
||||
!options.showOnlyHighlighted ||
|
||||
repository.isNewRelease ||
|
||||
repository.isStaleRelease,
|
||||
"error" in repository || !options.showOnlyHighlighted || repository.isNewRelease || repository.isStaleRelease,
|
||||
)
|
||||
.sort((repoA, repoB) => {
|
||||
if (repoA.latestReleaseAt === undefined) return -1;
|
||||
if (repoB.latestReleaseAt === undefined) return 1;
|
||||
if ("error" in repoA) return -1;
|
||||
if ("error" in repoB) return 1;
|
||||
return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1;
|
||||
}) as ReleasesRepositoryResponse[];
|
||||
|
||||
|
||||
1531
pnpm-lock.yaml
generated
1531
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -24,7 +24,7 @@
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^6.1.1",
|
||||
"typescript-eslint": "^8.46.1"
|
||||
"typescript-eslint": "^8.46.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
|
||||
@@ -7,7 +7,7 @@ runs:
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v5
|
||||
with:
|
||||
node-version: 22.20.0
|
||||
node-version: 24.10.0
|
||||
cache: "pnpm"
|
||||
|
||||
- shell: bash
|
||||
|
||||
Reference in New Issue
Block a user