Replace entire codebase with homarr-labs/homarr
This commit is contained in:
4
packages/definitions/eslint.config.js
Normal file
4
packages/definitions/eslint.config.js
Normal file
@@ -0,0 +1,4 @@
|
||||
import baseConfig from "@homarr/eslint-config/base";
|
||||
|
||||
/** @type {import('typescript-eslint').Config} */
|
||||
export default [...baseConfig];
|
||||
1
packages/definitions/index.ts
Normal file
1
packages/definitions/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src";
|
||||
38
packages/definitions/package.json
Normal file
38
packages/definitions/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "@homarr/definitions",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"postinstall": "tsx ./src/docs/codegen.ts",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@homarr/prettier-config",
|
||||
"dependencies": {
|
||||
"@homarr/common": "workspace:^0.1.0",
|
||||
"fast-xml-parser": "^5.3.3",
|
||||
"zod": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@homarr/eslint-config": "workspace:^0.2.0",
|
||||
"@homarr/prettier-config": "workspace:^0.1.0",
|
||||
"@homarr/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"tsx": "4.20.4",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
17
packages/definitions/src/_definition.ts
Normal file
17
packages/definitions/src/_definition.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const createDefinition = <const TKeys extends string[], TOptions extends { defaultValue: TKeys[number] } | void>(
|
||||
values: TKeys,
|
||||
options: TOptions,
|
||||
) => ({
|
||||
values,
|
||||
defaultValue: options?.defaultValue as TOptions extends {
|
||||
defaultValue: infer T;
|
||||
}
|
||||
? T
|
||||
: undefined,
|
||||
});
|
||||
|
||||
export type inferDefinitionType<TDefinition> = TDefinition extends {
|
||||
values: readonly (infer T)[];
|
||||
}
|
||||
? T
|
||||
: never;
|
||||
2
packages/definitions/src/auth.ts
Normal file
2
packages/definitions/src/auth.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const supportedAuthProviders = ["credentials", "oidc", "ldap"] as const;
|
||||
export type SupportedAuthProvider = (typeof supportedAuthProviders)[number];
|
||||
14
packages/definitions/src/board.ts
Normal file
14
packages/definitions/src/board.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { inferDefinitionType } from "./_definition";
|
||||
import { createDefinition } from "./_definition";
|
||||
|
||||
export const backgroundImageAttachments = createDefinition(["fixed", "scroll"], { defaultValue: "fixed" });
|
||||
export const backgroundImageRepeats = createDefinition(["repeat", "repeat-x", "repeat-y", "no-repeat"], {
|
||||
defaultValue: "no-repeat",
|
||||
});
|
||||
export const backgroundImageSizes = createDefinition(["cover", "contain"], {
|
||||
defaultValue: "cover",
|
||||
});
|
||||
|
||||
export type BackgroundImageAttachment = inferDefinitionType<typeof backgroundImageAttachments>;
|
||||
export type BackgroundImageRepeat = inferDefinitionType<typeof backgroundImageRepeats>;
|
||||
export type BackgroundImageSize = inferDefinitionType<typeof backgroundImageSizes>;
|
||||
2
packages/definitions/src/cookie.ts
Normal file
2
packages/definitions/src/cookie.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const colorSchemeCookieKey = "homarr.color-scheme";
|
||||
export const localeCookieKey = "homarr.locale";
|
||||
11
packages/definitions/src/docker.ts
Normal file
11
packages/definitions/src/docker.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const dockerContainerStates = [
|
||||
"created",
|
||||
"running",
|
||||
"paused",
|
||||
"restarting",
|
||||
"exited",
|
||||
"removing",
|
||||
"dead",
|
||||
] as const;
|
||||
|
||||
export type DockerContainerState = (typeof dockerContainerStates)[number];
|
||||
75
packages/definitions/src/docs/codegen.ts
Normal file
75
packages/definitions/src/docs/codegen.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path, { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { XMLParser } from "fast-xml-parser";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
import { createDocumentationLink } from "./index";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const removeCommonUrl = (url: string) => {
|
||||
return url.replace("https://homarr.dev", "");
|
||||
};
|
||||
|
||||
const sitemapSchema = z.object({
|
||||
urlset: z.object({
|
||||
url: z.array(
|
||||
z.object({
|
||||
loc: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
const fetchSitemapAsync = async () => {
|
||||
const response = await fetch(createDocumentationLink("/sitemap.xml"));
|
||||
return await response.text();
|
||||
};
|
||||
|
||||
const parseXml = (sitemapXml: string) => {
|
||||
const parser = new XMLParser();
|
||||
const data: unknown = parser.parse(sitemapXml);
|
||||
const result = sitemapSchema.safeParse(data);
|
||||
if (!result.success) {
|
||||
throw new Error("Invalid sitemap schema");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
const mapSitemapXmlToPaths = (sitemapData: z.infer<typeof sitemapSchema>) => {
|
||||
return sitemapData.urlset.url.map((url) => removeCommonUrl(url.loc));
|
||||
};
|
||||
|
||||
const createSitemapPathType = (paths: string[]) => {
|
||||
return "export type HomarrDocumentationPath =\n" + paths.map((path) => ` | "${path.replace(/\/$/, "")}"`).join("\n");
|
||||
};
|
||||
|
||||
const updateSitemapTypeFileAsync = async (sitemapPathType: string) => {
|
||||
const content =
|
||||
"// This file is auto-generated by the codegen script\n" +
|
||||
"// it uses the sitemap.xml to generate the HomarrDocumentationPath type\n" +
|
||||
sitemapPathType +
|
||||
";\n";
|
||||
|
||||
await fs.writeFile(path.join(__dirname, "homarr-docs-sitemap.ts"), content);
|
||||
};
|
||||
|
||||
/**
|
||||
* This script fetches the sitemap.xml and generates the HomarrDocumentationPath type
|
||||
* which is used for typesafe documentation links
|
||||
*/
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
const main = async () => {
|
||||
const sitemapXml = await fetchSitemapAsync();
|
||||
const sitemapData = parseXml(sitemapXml);
|
||||
const paths = mapSitemapXmlToPaths(sitemapData);
|
||||
// Adding sitemap as it's not in the sitemap.xml and we need it for this file
|
||||
paths.push("/sitemap.xml");
|
||||
const sitemapPathType = createSitemapPathType(paths);
|
||||
await updateSitemapTypeFileAsync(sitemapPathType);
|
||||
};
|
||||
|
||||
void main();
|
||||
233
packages/definitions/src/docs/homarr-docs-sitemap.ts
Normal file
233
packages/definitions/src/docs/homarr-docs-sitemap.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
// This file is auto-generated by the codegen script
|
||||
// it uses the sitemap.xml to generate the HomarrDocumentationPath type
|
||||
export type HomarrDocumentationPath =
|
||||
| "/about-us"
|
||||
| "/blog"
|
||||
| "/blog/2023/01/11/version0.11"
|
||||
| "/blog/2023/04/16/version0.12-more-widgets"
|
||||
| "/blog/2023/11/10/authentication"
|
||||
| "/blog/2023/12/22/updated-documentation"
|
||||
| "/blog/2024/09/23/version-1.0"
|
||||
| "/blog/2024/12/17/open-beta-1.0"
|
||||
| "/blog/2025/01/19/migration-guide-1.0"
|
||||
| "/blog/2025/08/02/using-argus"
|
||||
| "/blog/archive"
|
||||
| "/blog/authors"
|
||||
| "/blog/authors/ajnart"
|
||||
| "/blog/authors/manuel-rw"
|
||||
| "/blog/authors/meierschlumpf"
|
||||
| "/blog/authors/tagashi"
|
||||
| "/blog/authors/walkx"
|
||||
| "/blog/documentation-migration"
|
||||
| "/blog/tags"
|
||||
| "/blog/tags/authentication"
|
||||
| "/blog/tags/breaking-changes"
|
||||
| "/blog/tags/contributions"
|
||||
| "/blog/tags/design"
|
||||
| "/blog/tags/dnd"
|
||||
| "/blog/tags/docs"
|
||||
| "/blog/tags/documentation"
|
||||
| "/blog/tags/gridstack"
|
||||
| "/blog/tags/homarr"
|
||||
| "/blog/tags/migration"
|
||||
| "/blog/tags/notepad"
|
||||
| "/blog/tags/security"
|
||||
| "/blog/tags/translations"
|
||||
| "/blog/tags/update"
|
||||
| "/blog/tags/version"
|
||||
| "/blog/translations"
|
||||
| "/search"
|
||||
| "/docs/tags"
|
||||
| "/docs/tags/active-directory"
|
||||
| "/docs/tags/administration"
|
||||
| "/docs/tags/advanced"
|
||||
| "/docs/tags/analytics"
|
||||
| "/docs/tags/api"
|
||||
| "/docs/tags/apps"
|
||||
| "/docs/tags/background"
|
||||
| "/docs/tags/boards"
|
||||
| "/docs/tags/bookmarks"
|
||||
| "/docs/tags/caddy"
|
||||
| "/docs/tags/certificates"
|
||||
| "/docs/tags/code"
|
||||
| "/docs/tags/community"
|
||||
| "/docs/tags/configuration"
|
||||
| "/docs/tags/connections"
|
||||
| "/docs/tags/customization"
|
||||
| "/docs/tags/data-sources"
|
||||
| "/docs/tags/database"
|
||||
| "/docs/tags/developer"
|
||||
| "/docs/tags/development"
|
||||
| "/docs/tags/docker"
|
||||
| "/docs/tags/donation"
|
||||
| "/docs/tags/edit-mode"
|
||||
| "/docs/tags/env"
|
||||
| "/docs/tags/environment-variables"
|
||||
| "/docs/tags/getting-started"
|
||||
| "/docs/tags/google"
|
||||
| "/docs/tags/groups"
|
||||
| "/docs/tags/help"
|
||||
| "/docs/tags/icon-picker"
|
||||
| "/docs/tags/icon-repositories"
|
||||
| "/docs/tags/icons"
|
||||
| "/docs/tags/installation"
|
||||
| "/docs/tags/integration"
|
||||
| "/docs/tags/integrations"
|
||||
| "/docs/tags/interface"
|
||||
| "/docs/tags/jobs"
|
||||
| "/docs/tags/layout"
|
||||
| "/docs/tags/ldap"
|
||||
| "/docs/tags/management"
|
||||
| "/docs/tags/media"
|
||||
| "/docs/tags/oidc"
|
||||
| "/docs/tags/open-collective"
|
||||
| "/docs/tags/permissions"
|
||||
| "/docs/tags/pgid"
|
||||
| "/docs/tags/ping"
|
||||
| "/docs/tags/programming"
|
||||
| "/docs/tags/proxy"
|
||||
| "/docs/tags/puid"
|
||||
| "/docs/tags/redis"
|
||||
| "/docs/tags/responsive"
|
||||
| "/docs/tags/roles"
|
||||
| "/docs/tags/search"
|
||||
| "/docs/tags/search-engines"
|
||||
| "/docs/tags/security"
|
||||
| "/docs/tags/self-signed"
|
||||
| "/docs/tags/seo"
|
||||
| "/docs/tags/server"
|
||||
| "/docs/tags/settings"
|
||||
| "/docs/tags/sso"
|
||||
| "/docs/tags/tasks"
|
||||
| "/docs/tags/technical-documentation"
|
||||
| "/docs/tags/traefik"
|
||||
| "/docs/tags/translations"
|
||||
| "/docs/tags/unraid"
|
||||
| "/docs/tags/uploads"
|
||||
| "/docs/tags/users"
|
||||
| "/docs/tags/variables"
|
||||
| "/docs/advanced/command-line"
|
||||
| "/docs/advanced/command-line/fix-usernames"
|
||||
| "/docs/advanced/command-line/password-recovery"
|
||||
| "/docs/advanced/development/getting-started"
|
||||
| "/docs/advanced/development/kubernetes"
|
||||
| "/docs/advanced/environment-variables"
|
||||
| "/docs/advanced/icons"
|
||||
| "/docs/advanced/keyboard-shortcuts"
|
||||
| "/docs/advanced/proxy"
|
||||
| "/docs/advanced/running-as-different-user"
|
||||
| "/docs/advanced/single-sign-on"
|
||||
| "/docs/advanced/styling"
|
||||
| "/docs/category/advanced"
|
||||
| "/docs/category/community"
|
||||
| "/docs/category/developer-guides"
|
||||
| "/docs/category/getting-started"
|
||||
| "/docs/category/installation"
|
||||
| "/docs/category/installation-1"
|
||||
| "/docs/category/integrations"
|
||||
| "/docs/category/management"
|
||||
| "/docs/category/widgets"
|
||||
| "/docs/community/donate"
|
||||
| "/docs/community/faq"
|
||||
| "/docs/community/get-in-touch"
|
||||
| "/docs/community/license"
|
||||
| "/docs/community/translations"
|
||||
| "/docs/getting-started"
|
||||
| "/docs/getting-started/after-the-installation"
|
||||
| "/docs/getting-started/glossary"
|
||||
| "/docs/getting-started/installation/docker"
|
||||
| "/docs/getting-started/installation/easy-panel"
|
||||
| "/docs/getting-started/installation/helm"
|
||||
| "/docs/getting-started/installation/home-assistant"
|
||||
| "/docs/getting-started/installation/pika-pods"
|
||||
| "/docs/getting-started/installation/portainer"
|
||||
| "/docs/getting-started/installation/proxmox"
|
||||
| "/docs/getting-started/installation/qnap"
|
||||
| "/docs/getting-started/installation/railway"
|
||||
| "/docs/getting-started/installation/runtipi"
|
||||
| "/docs/getting-started/installation/saltbox"
|
||||
| "/docs/getting-started/installation/source"
|
||||
| "/docs/getting-started/installation/synology"
|
||||
| "/docs/getting-started/installation/unraid"
|
||||
| "/docs/integrations/adguard-home"
|
||||
| "/docs/integrations/aria2"
|
||||
| "/docs/integrations/codeberg"
|
||||
| "/docs/integrations/dash-dot"
|
||||
| "/docs/integrations/deluge"
|
||||
| "/docs/integrations/docker-hub"
|
||||
| "/docs/integrations/docker"
|
||||
| "/docs/integrations/emby"
|
||||
| "/docs/integrations/github-containerregistry"
|
||||
| "/docs/integrations/github"
|
||||
| "/docs/integrations/gitlab"
|
||||
| "/docs/integrations/home-assistant"
|
||||
| "/docs/integrations/ical"
|
||||
| "/docs/integrations/jellyfin"
|
||||
| "/docs/integrations/jellyseerr"
|
||||
| "/docs/integrations/kubernetes"
|
||||
| "/docs/integrations/lidarr"
|
||||
| "/docs/integrations/linux-server-io"
|
||||
| "/docs/integrations/nextcloud"
|
||||
| "/docs/integrations/npm"
|
||||
| "/docs/integrations/ntfy"
|
||||
| "/docs/integrations/nzbget"
|
||||
| "/docs/integrations/open-media-vault"
|
||||
| "/docs/integrations/opnsense"
|
||||
| "/docs/integrations/overseerr"
|
||||
| "/docs/integrations/pi-hole"
|
||||
| "/docs/integrations/plex"
|
||||
| "/docs/integrations/prowlarr"
|
||||
| "/docs/integrations/proxmox"
|
||||
| "/docs/integrations/q-bittorent"
|
||||
| "/docs/integrations/quay"
|
||||
| "/docs/integrations/radarr"
|
||||
| "/docs/integrations/readarr"
|
||||
| "/docs/integrations/sabnzbd"
|
||||
| "/docs/integrations/sonarr"
|
||||
| "/docs/integrations/tdarr"
|
||||
| "/docs/integrations/transmission"
|
||||
| "/docs/integrations/truenas"
|
||||
| "/docs/integrations/unifi-controller"
|
||||
| "/docs/integrations/unraid"
|
||||
| "/docs/management/api"
|
||||
| "/docs/management/apps"
|
||||
| "/docs/management/boards"
|
||||
| "/docs/management/certificates"
|
||||
| "/docs/management/integrations"
|
||||
| "/docs/management/media"
|
||||
| "/docs/management/search-engines"
|
||||
| "/docs/management/settings"
|
||||
| "/docs/management/tasks"
|
||||
| "/docs/management/users"
|
||||
| "/docs/widgets/app"
|
||||
| "/docs/widgets/bookmarks"
|
||||
| "/docs/widgets/calendar"
|
||||
| "/docs/widgets/clock"
|
||||
| "/docs/widgets/dns-hole-controls"
|
||||
| "/docs/widgets/dns-hole-summary"
|
||||
| "/docs/widgets/docker-containers"
|
||||
| "/docs/widgets/downloads"
|
||||
| "/docs/widgets/firewall"
|
||||
| "/docs/widgets/health-monitoring"
|
||||
| "/docs/widgets/iframe"
|
||||
| "/docs/widgets/indexer-manager"
|
||||
| "/docs/widgets/media-releases"
|
||||
| "/docs/widgets/media-request-list"
|
||||
| "/docs/widgets/media-request-stats"
|
||||
| "/docs/widgets/media-server"
|
||||
| "/docs/widgets/media-transcoding"
|
||||
| "/docs/widgets/minecraft-server-status"
|
||||
| "/docs/widgets/network-controller-status"
|
||||
| "/docs/widgets/network-controller-summary"
|
||||
| "/docs/widgets/notebook"
|
||||
| "/docs/widgets/notifications"
|
||||
| "/docs/widgets/releases"
|
||||
| "/docs/widgets/rss-feed"
|
||||
| "/docs/widgets/smart-home-entity-state"
|
||||
| "/docs/widgets/smart-home-execute-automation"
|
||||
| "/docs/widgets/stock-price"
|
||||
| "/docs/widgets/system-resources"
|
||||
| "/docs/widgets/video"
|
||||
| "/docs/widgets/weather"
|
||||
| ""
|
||||
| "/sitemap.xml";
|
||||
14
packages/definitions/src/docs/index.ts
Normal file
14
packages/definitions/src/docs/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { HomarrDocumentationPath } from "./homarr-docs-sitemap";
|
||||
|
||||
const documentationBaseUrl = "https://homarr.dev";
|
||||
|
||||
// Please use the method so the path can be checked!
|
||||
export const createDocumentationLink = (
|
||||
path: HomarrDocumentationPath,
|
||||
hashTag?: `#${string}`,
|
||||
queryParams?: Record<string, string>,
|
||||
) => {
|
||||
const url = `${documentationBaseUrl}${path}`;
|
||||
const params = queryParams ? `?${new URLSearchParams(queryParams)}` : "";
|
||||
return `${url}${params}${hashTag ?? ""}`;
|
||||
};
|
||||
1
packages/definitions/src/emptysuperjson.ts
Normal file
1
packages/definitions/src/emptysuperjson.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const emptySuperJSON = '{"json": {}}';
|
||||
2
packages/definitions/src/group.ts
Normal file
2
packages/definitions/src/group.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const everyoneGroup = "everyone";
|
||||
export const credentialsAdminGroup = "credentials-admin";
|
||||
6
packages/definitions/src/hotkeys.ts
Normal file
6
packages/definitions/src/hotkeys.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const hotkeys = {
|
||||
toggleBoardEdit: "mod+e",
|
||||
toggleColorScheme: "mod+j",
|
||||
saveNotebook: "mod+s",
|
||||
openSpotlight: "mod+k",
|
||||
};
|
||||
16
packages/definitions/src/index.ts
Normal file
16
packages/definitions/src/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export * from "./board";
|
||||
export * from "./integration";
|
||||
export * from "./section";
|
||||
export * from "./widget";
|
||||
export * from "./permissions";
|
||||
export * from "./docker";
|
||||
export * from "./kubernetes";
|
||||
export * from "./auth";
|
||||
export * from "./user";
|
||||
export * from "./group";
|
||||
export * from "./docs";
|
||||
export * from "./cookie";
|
||||
export * from "./search-engine";
|
||||
export * from "./onboarding";
|
||||
export * from "./emptysuperjson";
|
||||
export * from "./hotkeys";
|
||||
396
packages/definitions/src/integration.ts
Normal file
396
packages/definitions/src/integration.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
import { objectKeys } from "@homarr/common";
|
||||
import type { AtLeastOneOf } from "@homarr/common/types";
|
||||
|
||||
import { createDocumentationLink } from "./docs";
|
||||
|
||||
export const integrationSecretKindObject = {
|
||||
apiKey: { isPublic: false, multiline: false },
|
||||
username: { isPublic: true, multiline: false },
|
||||
password: { isPublic: false, multiline: false },
|
||||
tokenId: { isPublic: true, multiline: false },
|
||||
realm: { isPublic: true, multiline: false },
|
||||
personalAccessToken: { isPublic: false, multiline: false },
|
||||
topic: { isPublic: true, multiline: false },
|
||||
opnsenseApiKey: { isPublic: false, multiline: false },
|
||||
opnsenseApiSecret: { isPublic: false, multiline: false },
|
||||
url: { isPublic: false, multiline: false },
|
||||
privateKey: { isPublic: false, multiline: true },
|
||||
githubAppId: { isPublic: true, multiline: false },
|
||||
githubInstallationId: { isPublic: true, multiline: false },
|
||||
} satisfies Record<string, { isPublic: boolean; multiline: boolean }>;
|
||||
|
||||
export const integrationSecretKinds = objectKeys(integrationSecretKindObject);
|
||||
|
||||
interface integrationDefinition {
|
||||
name: string;
|
||||
iconUrl: string;
|
||||
secretKinds: AtLeastOneOf<IntegrationSecretKind[]>; // at least one secret kind set is required
|
||||
category: AtLeastOneOf<IntegrationCategory>;
|
||||
documentationUrl: string | null;
|
||||
defaultUrl?: string; // optional default URL for the integration
|
||||
}
|
||||
|
||||
export const integrationDefs = {
|
||||
sabNzbd: {
|
||||
name: "SABnzbd",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sabnzbd.svg",
|
||||
category: ["downloadClient", "usenet"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/sabnzbd"),
|
||||
},
|
||||
nzbGet: {
|
||||
name: "NZBGet",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nzbget.svg",
|
||||
category: ["downloadClient", "usenet"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/nzbget"),
|
||||
},
|
||||
deluge: {
|
||||
name: "Deluge",
|
||||
secretKinds: [["password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/deluge.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/deluge"),
|
||||
},
|
||||
transmission: {
|
||||
name: "Transmission",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/transmission.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/transmission"),
|
||||
},
|
||||
qBittorrent: {
|
||||
name: "qBittorrent",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/qbittorrent.svg",
|
||||
category: ["downloadClient", "torrent"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/q-bittorent"),
|
||||
},
|
||||
aria2: {
|
||||
name: "Aria2",
|
||||
secretKinds: [[], ["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/PapirusDevelopmentTeam/papirus_icons@latest/src/system_downloads_3.svg",
|
||||
category: ["downloadClient", "torrent", "miscellaneous"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/aria2"),
|
||||
},
|
||||
sonarr: {
|
||||
name: "Sonarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/sonarr.svg",
|
||||
category: ["calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/sonarr"),
|
||||
},
|
||||
radarr: {
|
||||
name: "Radarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/radarr.svg",
|
||||
category: ["calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/radarr"),
|
||||
},
|
||||
lidarr: {
|
||||
name: "Lidarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/lidarr.svg",
|
||||
category: ["calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/lidarr"),
|
||||
},
|
||||
readarr: {
|
||||
name: "Readarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/readarr.svg",
|
||||
category: ["calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/readarr"),
|
||||
},
|
||||
prowlarr: {
|
||||
name: "Prowlarr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/prowlarr.svg",
|
||||
category: ["indexerManager"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/prowlarr"),
|
||||
},
|
||||
jellyfin: {
|
||||
name: "Jellyfin",
|
||||
secretKinds: [["username", "password"], ["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyfin.svg",
|
||||
category: ["mediaService", "mediaRelease"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/jellyfin"),
|
||||
},
|
||||
emby: {
|
||||
name: "Emby",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/emby.svg",
|
||||
category: ["mediaService", "mediaRelease"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/emby"),
|
||||
},
|
||||
plex: {
|
||||
name: "Plex",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/plex.svg",
|
||||
category: ["mediaService", "mediaRelease"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/plex"),
|
||||
},
|
||||
jellyseerr: {
|
||||
name: "Jellyseerr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/jellyseerr.svg",
|
||||
category: ["mediaSearch", "mediaRequest", "search"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/jellyseerr"),
|
||||
},
|
||||
overseerr: {
|
||||
name: "Overseerr",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/overseerr.svg",
|
||||
category: ["mediaSearch", "mediaRequest", "search"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/overseerr"),
|
||||
},
|
||||
piHole: {
|
||||
name: "Pi-hole",
|
||||
secretKinds: [["apiKey"], []],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/pi-hole.svg",
|
||||
category: ["dnsHole"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/pi-hole"),
|
||||
},
|
||||
adGuardHome: {
|
||||
name: "AdGuard Home",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/adguard-home.svg",
|
||||
category: ["dnsHole"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/adguard-home"),
|
||||
},
|
||||
homeAssistant: {
|
||||
name: "Home Assistant",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/home-assistant.svg",
|
||||
category: ["smartHomeServer", "calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/home-assistant"),
|
||||
},
|
||||
openmediavault: {
|
||||
name: "OpenMediaVault",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/openmediavault.svg",
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/open-media-vault"),
|
||||
},
|
||||
dashDot: {
|
||||
name: "Dash.",
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/dashdot.png",
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/dash-dot"),
|
||||
},
|
||||
tdarr: {
|
||||
name: "Tdarr",
|
||||
secretKinds: [[], ["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/tdarr.png",
|
||||
category: ["mediaTranscoding"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/tdarr"),
|
||||
},
|
||||
proxmox: {
|
||||
name: "Proxmox",
|
||||
secretKinds: [["username", "tokenId", "apiKey", "realm"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/proxmox.svg",
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/proxmox"),
|
||||
},
|
||||
nextcloud: {
|
||||
name: "Nextcloud",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/nextcloud.svg",
|
||||
category: ["calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/nextcloud"),
|
||||
},
|
||||
unifiController: {
|
||||
name: "Unifi Controller",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/unifi.png",
|
||||
category: ["networkController"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/unifi-controller"),
|
||||
},
|
||||
opnsense: {
|
||||
name: "OPNsense",
|
||||
secretKinds: [["opnsenseApiKey", "opnsenseApiSecret"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/opnsense.svg",
|
||||
category: ["firewall"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/opnsense"),
|
||||
},
|
||||
github: {
|
||||
name: "Github",
|
||||
secretKinds: [[], ["personalAccessToken"], ["githubAppId", "githubInstallationId", "privateKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://api.github.com",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/github"),
|
||||
},
|
||||
dockerHub: {
|
||||
name: "Docker Hub",
|
||||
secretKinds: [[], ["username", "personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/docker.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://hub.docker.com",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/docker-hub"),
|
||||
},
|
||||
gitlab: {
|
||||
name: "Gitlab",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/gitlab.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://gitlab.com",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/gitlab"),
|
||||
},
|
||||
npm: {
|
||||
name: "NPM",
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/npm.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://registry.npmjs.org",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/npm"),
|
||||
},
|
||||
codeberg: {
|
||||
name: "Codeberg",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/codeberg.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://codeberg.org",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/codeberg"),
|
||||
},
|
||||
linuxServerIO: {
|
||||
name: "LinuxServer.io",
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/linuxserver-io.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://api.linuxserver.io",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/linux-server-io"),
|
||||
},
|
||||
gitHubContainerRegistry: {
|
||||
name: "GitHub Container Registry",
|
||||
secretKinds: [[], ["personalAccessToken"], ["githubAppId", "githubInstallationId", "privateKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/github.svg",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://api.github.com",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/github"),
|
||||
},
|
||||
quay: {
|
||||
name: "Quay",
|
||||
secretKinds: [[], ["personalAccessToken"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/png/quay.png",
|
||||
category: ["releasesProvider"],
|
||||
defaultUrl: "https://quay.io",
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/quay"),
|
||||
},
|
||||
ntfy: {
|
||||
name: "ntfy",
|
||||
secretKinds: [["topic"], ["topic", "apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ntfy.svg",
|
||||
category: ["notifications"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/ntfy"),
|
||||
},
|
||||
ical: {
|
||||
name: "iCal",
|
||||
secretKinds: [["url"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/ical.svg",
|
||||
category: ["calendar"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/ical"),
|
||||
},
|
||||
truenas: {
|
||||
name: "TrueNAS",
|
||||
secretKinds: [["username", "password"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/truenas.svg",
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/truenas"),
|
||||
},
|
||||
unraid: {
|
||||
name: "Unraid",
|
||||
secretKinds: [["apiKey"]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/unraid.svg",
|
||||
category: ["healthMonitoring"],
|
||||
documentationUrl: createDocumentationLink("/docs/integrations/unraid"),
|
||||
},
|
||||
// This integration only returns mock data, it is used during development (but can also be used in production by directly going to the create page)
|
||||
mock: {
|
||||
name: "Mock",
|
||||
secretKinds: [[]],
|
||||
iconUrl: "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons@master/svg/vitest.svg",
|
||||
category: [
|
||||
"calendar",
|
||||
"dnsHole",
|
||||
"downloadClient",
|
||||
"healthMonitoring",
|
||||
"indexerManager",
|
||||
"mediaRelease",
|
||||
"mediaRequest",
|
||||
"mediaService",
|
||||
"mediaTranscoding",
|
||||
"networkController",
|
||||
"notifications",
|
||||
"smartHomeServer",
|
||||
],
|
||||
documentationUrl: null,
|
||||
},
|
||||
} as const satisfies Record<string, integrationDefinition>;
|
||||
|
||||
export const integrationKinds = objectKeys(integrationDefs) as AtLeastOneOf<IntegrationKind>;
|
||||
|
||||
export const getIconUrl = (integration: IntegrationKind) => integrationDefs[integration].iconUrl;
|
||||
|
||||
export const getIntegrationName = (integration: IntegrationKind) => integrationDefs[integration].name;
|
||||
|
||||
export const getDefaultSecretKinds = (integration: IntegrationKind): IntegrationSecretKind[] =>
|
||||
integrationDefs[integration].secretKinds[0];
|
||||
|
||||
export const getAllSecretKindOptions = (integration: IntegrationKind): AtLeastOneOf<IntegrationSecretKind[]> =>
|
||||
integrationDefs[integration].secretKinds;
|
||||
|
||||
export const getIntegrationDefaultUrl = (integration: IntegrationKind) => {
|
||||
const definition = integrationDefs[integration];
|
||||
return "defaultUrl" in definition ? definition.defaultUrl : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all integration kinds that share a category, typed only by the kinds belonging to the category
|
||||
* @param category Category to filter by, belonging to IntegrationCategory
|
||||
* @returns Partial list of integration kinds
|
||||
*/
|
||||
export const getIntegrationKindsByCategory = <TCategory extends IntegrationCategory>(category: TCategory) => {
|
||||
return objectKeys(integrationDefs).filter((integration) =>
|
||||
integrationDefs[integration].category.some((defCategory) => defCategory === category),
|
||||
) as AtLeastOneOf<IntegrationKindByCategory<TCategory>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Directly get the types of the list returned by getIntegrationKindsByCategory
|
||||
*/
|
||||
export type IntegrationKindByCategory<TCategory extends IntegrationCategory> = {
|
||||
[Key in keyof typeof integrationDefs]: TCategory extends (typeof integrationDefs)[Key]["category"][number]
|
||||
? Key
|
||||
: never;
|
||||
}[keyof typeof integrationDefs] extends infer U
|
||||
? //Needed to simplify the type when using it
|
||||
U
|
||||
: never;
|
||||
|
||||
export type IntegrationSecretKind = keyof typeof integrationSecretKindObject;
|
||||
export type IntegrationKind = keyof typeof integrationDefs;
|
||||
|
||||
export const integrationCategories = [
|
||||
"dnsHole",
|
||||
"mediaService",
|
||||
"calendar",
|
||||
"mediaSearch",
|
||||
"mediaRelease",
|
||||
"mediaRequest",
|
||||
"downloadClient",
|
||||
"usenet",
|
||||
"torrent",
|
||||
"miscellaneous",
|
||||
"smartHomeServer",
|
||||
"indexerManager",
|
||||
"healthMonitoring",
|
||||
"search",
|
||||
"mediaTranscoding",
|
||||
"networkController",
|
||||
"releasesProvider",
|
||||
"notifications",
|
||||
"firewall",
|
||||
] as const;
|
||||
|
||||
export type IntegrationCategory = (typeof integrationCategories)[number];
|
||||
110
packages/definitions/src/kubernetes.ts
Normal file
110
packages/definitions/src/kubernetes.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
export const kubernetesNodeStates = ["Ready", "NotReady"] as const;
|
||||
export const kubernetesNamespaceStates = ["Active", "Terminating"] as const;
|
||||
export const kubernetesResourceTypes = ["Reserved", "Used"] as const;
|
||||
export const kubernetesCapacityTypes = ["Pods", "CPU", "Memory"] as const;
|
||||
export const kubernetesLabelResourceTypes = [
|
||||
"configmaps",
|
||||
"pods",
|
||||
"ingresses",
|
||||
"namespaces",
|
||||
"nodes",
|
||||
"secrets",
|
||||
"services",
|
||||
"volumes",
|
||||
] as const;
|
||||
|
||||
export type KubernetesNodeState = (typeof kubernetesNodeStates)[number];
|
||||
export type KubernetesNamespaceState = (typeof kubernetesNamespaceStates)[number];
|
||||
export type KubernetesResourceType = (typeof kubernetesResourceTypes)[number];
|
||||
export type KubernetesCapacityType = (typeof kubernetesCapacityTypes)[number];
|
||||
export type KubernetesLabelResourceType = (typeof kubernetesLabelResourceTypes)[number];
|
||||
|
||||
export interface KubernetesBaseResource {
|
||||
name: string;
|
||||
namespace?: string;
|
||||
creationTimestamp?: Date;
|
||||
}
|
||||
|
||||
export interface KubernetesVolume extends KubernetesBaseResource {
|
||||
accessModes: string[];
|
||||
storage: string;
|
||||
storageClassName: string;
|
||||
volumeMode: string;
|
||||
volumeName: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface KubernetesSecret extends KubernetesBaseResource {
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface KubernetesPod extends KubernetesBaseResource {
|
||||
image?: string;
|
||||
applicationType: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface KubernetesService extends KubernetesBaseResource {
|
||||
type: string;
|
||||
ports?: string[];
|
||||
targetPorts?: string[];
|
||||
clusterIP: string;
|
||||
}
|
||||
|
||||
export interface KubernetesIngressPath {
|
||||
serviceName: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface KubernetesIngressRuleAndPath {
|
||||
host: string;
|
||||
paths: KubernetesIngressPath[];
|
||||
}
|
||||
|
||||
export interface KubernetesIngress extends KubernetesBaseResource {
|
||||
className: string;
|
||||
rulesAndPaths: KubernetesIngressRuleAndPath[];
|
||||
}
|
||||
|
||||
export interface KubernetesNamespace extends KubernetesBaseResource {
|
||||
status: KubernetesNamespaceState;
|
||||
}
|
||||
|
||||
export interface KubernetesNode {
|
||||
name: string;
|
||||
status: KubernetesNodeState;
|
||||
allocatableCpuPercentage: number;
|
||||
allocatableRamPercentage: number;
|
||||
podsCount: number;
|
||||
operatingSystem?: string;
|
||||
architecture?: string;
|
||||
kubernetesVersion?: string;
|
||||
creationTimestamp?: Date;
|
||||
}
|
||||
|
||||
export interface KubernetesCluster {
|
||||
name: string;
|
||||
providers: string;
|
||||
kubernetesVersion: string;
|
||||
architecture: string;
|
||||
nodeCount: number;
|
||||
capacity: KubernetesCapacity[];
|
||||
}
|
||||
|
||||
export interface KubernetesCapacity {
|
||||
type: KubernetesCapacityType;
|
||||
resourcesStats: KubernetesResourceStat[];
|
||||
}
|
||||
|
||||
export interface KubernetesResourceStat {
|
||||
percentageValue: number;
|
||||
type: KubernetesResourceType;
|
||||
capacityUnit?: string;
|
||||
usedValue: number;
|
||||
maxUsedValue: number;
|
||||
}
|
||||
|
||||
export interface ClusterResourceCount {
|
||||
label: string;
|
||||
count: number;
|
||||
}
|
||||
2
packages/definitions/src/onboarding.ts
Normal file
2
packages/definitions/src/onboarding.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const onboardingSteps = ["start", "import", "user", "group", "settings", "finish"] as const;
|
||||
export type OnboardingStep = (typeof onboardingSteps)[number];
|
||||
119
packages/definitions/src/permissions.ts
Normal file
119
packages/definitions/src/permissions.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { objectEntries, objectKeys } from "@homarr/common";
|
||||
|
||||
/**
|
||||
* Permissions for boards.
|
||||
* view: Can view the board and its content. (e.g. see all items on the board, but not modify them)
|
||||
* modify: Can modify the board, its content and visual settings. (e.g. move items, change the background)
|
||||
* full: Can modify the board, its content, visual settings, access settings, delete, change the visibility and rename. (e.g. change the board name, delete the board, give access to other users)
|
||||
*/
|
||||
export const boardPermissions = ["view", "modify", "full"] as const;
|
||||
export const boardPermissionsMap = {
|
||||
view: "board-view-all",
|
||||
modify: "board-modify-all",
|
||||
full: "board-full-all",
|
||||
} satisfies Record<BoardPermission, GroupPermissionKey>;
|
||||
|
||||
export type BoardPermission = (typeof boardPermissions)[number];
|
||||
|
||||
/**
|
||||
* Permissions for integrations.
|
||||
* use: Can select the integration for an item on the board. (e.g. select pi-hole for a widget)
|
||||
* interact: Can interact with the integration. (e.g. enable / disable pi-hole)
|
||||
* full: Can modify the integration. (e.g. change the pi-hole url, secrets and access settings)
|
||||
*/
|
||||
export const integrationPermissions = ["use", "interact", "full"] as const;
|
||||
export const integrationPermissionsMap = {
|
||||
use: "integration-use-all",
|
||||
interact: "integration-interact-all",
|
||||
full: "integration-full-all",
|
||||
} satisfies Record<IntegrationPermission, GroupPermissionKey>;
|
||||
|
||||
export type IntegrationPermission = (typeof integrationPermissions)[number];
|
||||
|
||||
/**
|
||||
* Global permissions that can be assigned to groups.
|
||||
* The keys are generated through combining the key and all array items.
|
||||
* For example "board-create" is a generated key
|
||||
*/
|
||||
export const groupPermissions = {
|
||||
// Order is the same in the UI, inspired from order in navigation here
|
||||
board: ["create", "view-all", "modify-all", "full-all"],
|
||||
app: ["create", "use-all", "modify-all", "full-all"],
|
||||
integration: ["create", "use-all", "interact-all", "full-all"],
|
||||
"search-engine": ["create", "modify-all", "full-all"],
|
||||
media: ["upload", "view-all", "full-all"],
|
||||
other: ["view-logs"],
|
||||
admin: true,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* In the following object is described how the permissions are related to each other.
|
||||
* For example everybody with the permission "board-modify-all" also has the permission "board-view-all".
|
||||
* Or admin has all permissions (board-full-all and integration-full-all which will resolve in an array of every permission).
|
||||
*/
|
||||
const groupPermissionParents = {
|
||||
"board-modify-all": ["board-view-all"],
|
||||
"board-full-all": ["board-modify-all", "board-create"],
|
||||
"app-modify-all": ["app-create"],
|
||||
"app-full-all": ["app-modify-all", "app-use-all"],
|
||||
"integration-interact-all": ["integration-use-all"],
|
||||
"integration-full-all": ["integration-interact-all", "integration-create"],
|
||||
"search-engine-modify-all": ["search-engine-create"],
|
||||
"search-engine-full-all": ["search-engine-modify-all"],
|
||||
"media-full-all": ["media-upload", "media-view-all"],
|
||||
admin: [
|
||||
"board-full-all",
|
||||
"app-full-all",
|
||||
"integration-full-all",
|
||||
"search-engine-full-all",
|
||||
"media-full-all",
|
||||
"other-view-logs",
|
||||
],
|
||||
} satisfies Partial<Record<GroupPermissionKey, GroupPermissionKey[]>>;
|
||||
|
||||
export const getPermissionsWithParents = (permissions: GroupPermissionKey[]): GroupPermissionKey[] => {
|
||||
const res = permissions.map((permission) => {
|
||||
return objectEntries(groupPermissionParents)
|
||||
.filter(([_key, value]: [string, GroupPermissionKey[]]) => value.includes(permission))
|
||||
.map(([key]) => getPermissionsWithParents([key]))
|
||||
.flat();
|
||||
});
|
||||
|
||||
return permissions.concat(res.flat());
|
||||
};
|
||||
|
||||
const getPermissionsInner = (permissionSet: Set<GroupPermissionKey>, permissions: GroupPermissionKey[]) => {
|
||||
permissions.forEach((permission) => {
|
||||
const children = groupPermissionParents[permission as keyof typeof groupPermissionParents];
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (children) {
|
||||
getPermissionsInner(permissionSet, children);
|
||||
}
|
||||
|
||||
permissionSet.add(permission);
|
||||
});
|
||||
};
|
||||
|
||||
export const getPermissionsWithChildren = (permissions: GroupPermissionKey[]) => {
|
||||
const permissionSet = new Set<GroupPermissionKey>();
|
||||
getPermissionsInner(permissionSet, permissions);
|
||||
return Array.from(permissionSet);
|
||||
};
|
||||
|
||||
type GroupPermissions = typeof groupPermissions;
|
||||
|
||||
export type GroupPermissionKey = {
|
||||
[key in keyof GroupPermissions]: GroupPermissions[key] extends readonly string[]
|
||||
? `${key}-${GroupPermissions[key][number]}`
|
||||
: key;
|
||||
}[keyof GroupPermissions];
|
||||
|
||||
export const groupPermissionKeys = objectKeys(groupPermissions).reduce((acc, key) => {
|
||||
const item = groupPermissions[key];
|
||||
if (typeof item !== "boolean") {
|
||||
acc.push(...item.map((subKey) => `${key}-${subKey}` as GroupPermissionKey));
|
||||
} else {
|
||||
acc.push(key as GroupPermissionKey);
|
||||
}
|
||||
return acc;
|
||||
}, [] as GroupPermissionKey[]);
|
||||
2
packages/definitions/src/search-engine.ts
Normal file
2
packages/definitions/src/search-engine.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const searchEngineTypes = ["generic", "fromIntegration"] as const;
|
||||
export type SearchEngineType = (typeof searchEngineTypes)[number];
|
||||
2
packages/definitions/src/section.ts
Normal file
2
packages/definitions/src/section.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const sectionKinds = ["category", "empty", "dynamic"] as const;
|
||||
export type SectionKind = (typeof sectionKinds)[number];
|
||||
47
packages/definitions/src/test/docs.spec.ts
Normal file
47
packages/definitions/src/test/docs.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import { createDocumentationLink } from "../docs";
|
||||
import type { HomarrDocumentationPath } from "../docs/homarr-docs-sitemap";
|
||||
|
||||
describe("createDocumentationLink should generate correct URLs", () => {
|
||||
test.each([
|
||||
["/docs/getting-started", undefined, undefined, "https://homarr.dev/docs/getting-started"],
|
||||
["/blog", undefined, undefined, "https://homarr.dev/blog"],
|
||||
["/docs/widgets/weather", "#configuration", undefined, "https://homarr.dev/docs/widgets/weather#configuration"],
|
||||
[
|
||||
"/docs/advanced/environment-variables",
|
||||
undefined,
|
||||
{ lang: "en" },
|
||||
"https://homarr.dev/docs/advanced/environment-variables?lang=en",
|
||||
],
|
||||
[
|
||||
"/docs/widgets/bookmarks",
|
||||
"#sorting",
|
||||
{ lang: "fr", theme: "dark" },
|
||||
"https://homarr.dev/docs/widgets/bookmarks?lang=fr&theme=dark#sorting",
|
||||
],
|
||||
] satisfies [HomarrDocumentationPath, `#${string}` | undefined, Record<string, string> | undefined, string][])(
|
||||
"should create correct URL for path %s with hash %s and params %o",
|
||||
(path, hashTag, queryParams, expected) => {
|
||||
expect(createDocumentationLink(path, hashTag, queryParams)).toBe(expected);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("createDocumentationLink parameter validation", () => {
|
||||
test("should work with only path parameter", () => {
|
||||
const result = createDocumentationLink("/docs/getting-started");
|
||||
expect(result).toBe("https://homarr.dev/docs/getting-started");
|
||||
});
|
||||
|
||||
test("should work with path and hashtag", () => {
|
||||
const result = createDocumentationLink("/docs/getting-started", "#installation");
|
||||
expect(result).toBe("https://homarr.dev/docs/getting-started#installation");
|
||||
});
|
||||
|
||||
test("should work with path and query params", () => {
|
||||
const result = createDocumentationLink("/docs/getting-started", undefined, { version: "1.0" });
|
||||
expect(result).toBe("https://homarr.dev/docs/getting-started?version=1.0");
|
||||
});
|
||||
});
|
||||
14
packages/definitions/src/test/integration.spec.ts
Normal file
14
packages/definitions/src/test/integration.spec.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { objectEntries } from "@homarr/common";
|
||||
|
||||
import { integrationDefs } from "../integration";
|
||||
|
||||
describe("Icon url's of integrations should be valid and return 200", () => {
|
||||
objectEntries(integrationDefs).forEach(([integration, { iconUrl }]) => {
|
||||
it.concurrent(`should return 200 for ${integration}`, async () => {
|
||||
const res = await fetch(iconUrl);
|
||||
expect(res.status).toBe(200);
|
||||
});
|
||||
});
|
||||
});
|
||||
47
packages/definitions/src/test/permissions.spec.ts
Normal file
47
packages/definitions/src/test/permissions.spec.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, test } from "vitest";
|
||||
|
||||
import type { GroupPermissionKey } from "../permissions";
|
||||
import { getPermissionsWithChildren, getPermissionsWithParents } from "../permissions";
|
||||
|
||||
describe("getPermissionsWithParents should return the correct permissions", () => {
|
||||
test.each([
|
||||
[["board-view-all"], ["board-view-all", "board-modify-all", "board-full-all", "admin"]],
|
||||
[["board-modify-all"], ["board-modify-all", "board-full-all", "admin"]],
|
||||
[["board-create"], ["board-create", "board-full-all", "admin"]],
|
||||
[["board-full-all"], ["board-full-all", "admin"]],
|
||||
[["integration-use-all"], ["integration-use-all", "integration-interact-all", "integration-full-all", "admin"]],
|
||||
[["integration-create"], ["integration-create", "integration-full-all", "admin"]],
|
||||
[["integration-interact-all"], ["integration-interact-all", "integration-full-all", "admin"]],
|
||||
[["integration-full-all"], ["integration-full-all", "admin"]],
|
||||
[["admin"], ["admin"]],
|
||||
] satisfies [GroupPermissionKey[], GroupPermissionKey[]][])("expect %s to return %s", (input, expectedOutput) => {
|
||||
expect(getPermissionsWithParents(input)).toEqual(expect.arrayContaining(expectedOutput));
|
||||
});
|
||||
});
|
||||
|
||||
describe("getPermissionsWithChildren should return the correct permissions", () => {
|
||||
test.each([
|
||||
[["board-view-all"], ["board-view-all"]],
|
||||
[["board-modify-all"], ["board-view-all", "board-modify-all"]],
|
||||
[["board-create"], ["board-create"]],
|
||||
[["board-full-all"], ["board-full-all", "board-modify-all", "board-view-all"]],
|
||||
[["integration-use-all"], ["integration-use-all"]],
|
||||
[["integration-create"], ["integration-create"]],
|
||||
[["integration-interact-all"], ["integration-interact-all", "integration-use-all"]],
|
||||
[["integration-full-all"], ["integration-full-all", "integration-interact-all", "integration-use-all"]],
|
||||
[
|
||||
["admin"],
|
||||
[
|
||||
"admin",
|
||||
"board-full-all",
|
||||
"board-modify-all",
|
||||
"board-view-all",
|
||||
"integration-full-all",
|
||||
"integration-interact-all",
|
||||
"integration-use-all",
|
||||
],
|
||||
],
|
||||
] satisfies [GroupPermissionKey[], GroupPermissionKey[]][])("expect %s to return %s", (input, expectedOutput) => {
|
||||
expect(getPermissionsWithChildren(input)).toEqual(expect.arrayContaining(expectedOutput));
|
||||
});
|
||||
});
|
||||
2
packages/definitions/src/user.ts
Normal file
2
packages/definitions/src/user.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const colorSchemes = ["light", "dark"] as const;
|
||||
export type ColorScheme = (typeof colorSchemes)[number];
|
||||
33
packages/definitions/src/widget.ts
Normal file
33
packages/definitions/src/widget.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export const widgetKinds = [
|
||||
"clock",
|
||||
"weather",
|
||||
"app",
|
||||
"iframe",
|
||||
"video",
|
||||
"notebook",
|
||||
"dnsHoleSummary",
|
||||
"dnsHoleControls",
|
||||
"smartHome-entityState",
|
||||
"smartHome-executeAutomation",
|
||||
"stockPrice",
|
||||
"mediaServer",
|
||||
"calendar",
|
||||
"downloads",
|
||||
"mediaRequests-requestList",
|
||||
"mediaRequests-requestStats",
|
||||
"mediaTranscoding",
|
||||
"minecraftServerStatus",
|
||||
"networkControllerSummary",
|
||||
"networkControllerStatus",
|
||||
"rssFeed",
|
||||
"bookmarks",
|
||||
"indexerManager",
|
||||
"healthMonitoring",
|
||||
"releases",
|
||||
"mediaReleases",
|
||||
"dockerContainers",
|
||||
"firewall",
|
||||
"notifications",
|
||||
"systemResources",
|
||||
] as const;
|
||||
export type WidgetKind = (typeof widgetKinds)[number];
|
||||
8
packages/definitions/tsconfig.json
Normal file
8
packages/definitions/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@homarr/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user