diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index 60685d84d6..066dc9c701 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -36,6 +36,10 @@ services: IMMICH_BUILD_URL: https://github.com/immich-app/immich/actions/runs/9654404849 IMMICH_BUILD_IMAGE: development IMMICH_BUILD_IMAGE_URL: https://github.com/immich-app/immich/pkgs/container/immich-server + IMMICH_THIRD_PARTY_SOURCE_URL: https://github.com/immich-app/immich/ + IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues + IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://immich.app/docs + IMMICH_THIRD_PARTY_SUPPORT_URL: https://immich.app/docs/third-party ulimits: nofile: soft: 1048576 diff --git a/mobile/openapi/lib/model/server_about_response_dto.dart b/mobile/openapi/lib/model/server_about_response_dto.dart index 1ab51a80f1..5d53d5fdee 100644 --- a/mobile/openapi/lib/model/server_about_response_dto.dart +++ b/mobile/openapi/lib/model/server_about_response_dto.dart @@ -28,6 +28,10 @@ class ServerAboutResponseDto { this.sourceCommit, this.sourceRef, this.sourceUrl, + this.thirdPartyBugFeatureUrl, + this.thirdPartyDocumentationUrl, + this.thirdPartySourceUrl, + this.thirdPartySupportUrl, required this.version, required this.versionUrl, }); @@ -146,6 +150,38 @@ class ServerAboutResponseDto { /// String? sourceUrl; + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartyBugFeatureUrl; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartyDocumentationUrl; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartySourceUrl; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? thirdPartySupportUrl; + String version; String versionUrl; @@ -167,6 +203,10 @@ class ServerAboutResponseDto { other.sourceCommit == sourceCommit && other.sourceRef == sourceRef && other.sourceUrl == sourceUrl && + other.thirdPartyBugFeatureUrl == thirdPartyBugFeatureUrl && + other.thirdPartyDocumentationUrl == thirdPartyDocumentationUrl && + other.thirdPartySourceUrl == thirdPartySourceUrl && + other.thirdPartySupportUrl == thirdPartySupportUrl && other.version == version && other.versionUrl == versionUrl; @@ -188,11 +228,15 @@ class ServerAboutResponseDto { (sourceCommit == null ? 0 : sourceCommit!.hashCode) + (sourceRef == null ? 0 : sourceRef!.hashCode) + (sourceUrl == null ? 0 : sourceUrl!.hashCode) + + (thirdPartyBugFeatureUrl == null ? 0 : thirdPartyBugFeatureUrl!.hashCode) + + (thirdPartyDocumentationUrl == null ? 0 : thirdPartyDocumentationUrl!.hashCode) + + (thirdPartySourceUrl == null ? 0 : thirdPartySourceUrl!.hashCode) + + (thirdPartySupportUrl == null ? 0 : thirdPartySupportUrl!.hashCode) + (version.hashCode) + (versionUrl.hashCode); @override - String toString() => 'ServerAboutResponseDto[build=$build, buildImage=$buildImage, buildImageUrl=$buildImageUrl, buildUrl=$buildUrl, exiftool=$exiftool, ffmpeg=$ffmpeg, imagemagick=$imagemagick, libvips=$libvips, licensed=$licensed, nodejs=$nodejs, repository=$repository, repositoryUrl=$repositoryUrl, sourceCommit=$sourceCommit, sourceRef=$sourceRef, sourceUrl=$sourceUrl, version=$version, versionUrl=$versionUrl]'; + String toString() => 'ServerAboutResponseDto[build=$build, buildImage=$buildImage, buildImageUrl=$buildImageUrl, buildUrl=$buildUrl, exiftool=$exiftool, ffmpeg=$ffmpeg, imagemagick=$imagemagick, libvips=$libvips, licensed=$licensed, nodejs=$nodejs, repository=$repository, repositoryUrl=$repositoryUrl, sourceCommit=$sourceCommit, sourceRef=$sourceRef, sourceUrl=$sourceUrl, thirdPartyBugFeatureUrl=$thirdPartyBugFeatureUrl, thirdPartyDocumentationUrl=$thirdPartyDocumentationUrl, thirdPartySourceUrl=$thirdPartySourceUrl, thirdPartySupportUrl=$thirdPartySupportUrl, version=$version, versionUrl=$versionUrl]'; Map toJson() { final json = {}; @@ -266,6 +310,26 @@ class ServerAboutResponseDto { json[r'sourceUrl'] = this.sourceUrl; } else { // json[r'sourceUrl'] = null; + } + if (this.thirdPartyBugFeatureUrl != null) { + json[r'thirdPartyBugFeatureUrl'] = this.thirdPartyBugFeatureUrl; + } else { + // json[r'thirdPartyBugFeatureUrl'] = null; + } + if (this.thirdPartyDocumentationUrl != null) { + json[r'thirdPartyDocumentationUrl'] = this.thirdPartyDocumentationUrl; + } else { + // json[r'thirdPartyDocumentationUrl'] = null; + } + if (this.thirdPartySourceUrl != null) { + json[r'thirdPartySourceUrl'] = this.thirdPartySourceUrl; + } else { + // json[r'thirdPartySourceUrl'] = null; + } + if (this.thirdPartySupportUrl != null) { + json[r'thirdPartySupportUrl'] = this.thirdPartySupportUrl; + } else { + // json[r'thirdPartySupportUrl'] = null; } json[r'version'] = this.version; json[r'versionUrl'] = this.versionUrl; @@ -296,6 +360,10 @@ class ServerAboutResponseDto { sourceCommit: mapValueOfType(json, r'sourceCommit'), sourceRef: mapValueOfType(json, r'sourceRef'), sourceUrl: mapValueOfType(json, r'sourceUrl'), + thirdPartyBugFeatureUrl: mapValueOfType(json, r'thirdPartyBugFeatureUrl'), + thirdPartyDocumentationUrl: mapValueOfType(json, r'thirdPartyDocumentationUrl'), + thirdPartySourceUrl: mapValueOfType(json, r'thirdPartySourceUrl'), + thirdPartySupportUrl: mapValueOfType(json, r'thirdPartySupportUrl'), version: mapValueOfType(json, r'version')!, versionUrl: mapValueOfType(json, r'versionUrl')!, ); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 970230f4e3..665b50420c 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -10780,6 +10780,18 @@ "sourceUrl": { "type": "string" }, + "thirdPartyBugFeatureUrl": { + "type": "string" + }, + "thirdPartyDocumentationUrl": { + "type": "string" + }, + "thirdPartySourceUrl": { + "type": "string" + }, + "thirdPartySupportUrl": { + "type": "string" + }, "version": { "type": "string" }, diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index aa3501079b..40328718bb 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -917,6 +917,10 @@ export type ServerAboutResponseDto = { sourceCommit?: string; sourceRef?: string; sourceUrl?: string; + thirdPartyBugFeatureUrl?: string; + thirdPartyDocumentationUrl?: string; + thirdPartySourceUrl?: string; + thirdPartySupportUrl?: string; version: string; versionUrl: string; }; diff --git a/server/src/config.ts b/server/src/config.ts index 53374d581f..2e11f740d3 100644 --- a/server/src/config.ts +++ b/server/src/config.ts @@ -415,6 +415,10 @@ export const getBuildMetadata = () => ({ sourceRef: process.env.IMMICH_SOURCE_REF, sourceCommit: process.env.IMMICH_SOURCE_COMMIT, sourceUrl: process.env.IMMICH_SOURCE_URL, + thirdPartySourceUrl: process.env.IMMICH_THIRD_PARTY_SOURCE_URL, + thirdPartyBugFeatureUrl: process.env.IMMICH_THIRD_PARTY_BUG_FEATURE_URL, + thirdPartyDocumentationUrl: process.env.IMMICH_THIRD_PARTY_DOCUMENTATION_URL, + thirdPartySupportUrl: process.env.IMMICH_THIRD_PARTY_SUPPORT_URL, }); const clientLicensePublicKeyProd = diff --git a/server/src/dtos/server.dto.ts b/server/src/dtos/server.dto.ts index aafadff478..3d21987ccf 100644 --- a/server/src/dtos/server.dto.ts +++ b/server/src/dtos/server.dto.ts @@ -30,6 +30,11 @@ export class ServerAboutResponseDto { exiftool?: string; licensed!: boolean; + + thirdPartySourceUrl?: string; + thirdPartyBugFeatureUrl?: string; + thirdPartyDocumentationUrl?: string; + thirdPartySupportUrl?: string; } export class ServerStorageResponseDto { diff --git a/web/src/lib/assets/svg-paths.ts b/web/src/lib/assets/svg-paths.ts index cc8d0a1800..9c37849fcc 100644 --- a/web/src/lib/assets/svg-paths.ts +++ b/web/src/lib/assets/svg-paths.ts @@ -4,3 +4,6 @@ export const sunPath = export const moonViewBox = '0 0 20 20'; export const sunViewBox = '0 0 20 20'; + +export const discordPath = + 'M 9.1367188 3.8691406 C 9.1217187 3.8691406 9.1067969 3.8700938 9.0917969 3.8710938 C 8.9647969 3.8810937 5.9534375 4.1403594 4.0234375 5.6933594 C 3.0154375 6.6253594 1 12.073203 1 16.783203 C 1 16.866203 1.0215 16.946531 1.0625 17.019531 C 2.4535 19.462531 6.2473281 20.102859 7.1113281 20.130859 L 7.1269531 20.130859 C 7.2799531 20.130859 7.4236719 20.057594 7.5136719 19.933594 L 8.3886719 18.732422 C 6.0296719 18.122422 4.8248594 17.086391 4.7558594 17.025391 C 4.5578594 16.850391 4.5378906 16.549563 4.7128906 16.351562 C 4.8068906 16.244563 4.9383125 16.189453 5.0703125 16.189453 C 5.1823125 16.189453 5.2957188 16.228594 5.3867188 16.308594 C 5.4157187 16.334594 7.6340469 18.216797 11.998047 18.216797 C 16.370047 18.216797 18.589328 16.325641 18.611328 16.306641 C 18.702328 16.227641 18.815734 16.189453 18.927734 16.189453 C 19.059734 16.189453 19.190156 16.243562 19.285156 16.351562 C 19.459156 16.549563 19.441141 16.851391 19.244141 17.025391 C 19.174141 17.087391 17.968375 18.120469 15.609375 18.730469 L 16.484375 19.933594 C 16.574375 20.057594 16.718094 20.130859 16.871094 20.130859 L 16.886719 20.130859 C 17.751719 20.103859 21.5465 19.463531 22.9375 17.019531 C 22.9785 16.947531 23 16.866203 23 16.783203 C 23 12.073203 20.984172 6.624875 19.951172 5.671875 C 18.047172 4.140875 15.036203 3.8820937 14.908203 3.8710938 C 14.895203 3.8700938 14.880188 3.8691406 14.867188 3.8691406 C 14.681188 3.8691406 14.510594 3.9793906 14.433594 4.1503906 C 14.427594 4.1623906 14.362062 4.3138281 14.289062 4.5488281 C 15.548063 4.7608281 17.094141 5.1895937 18.494141 6.0585938 C 18.718141 6.1975938 18.787437 6.4917969 18.648438 6.7167969 C 18.558438 6.8627969 18.402188 6.9433594 18.242188 6.9433594 C 18.156188 6.9433594 18.069234 6.9200937 17.990234 6.8710938 C 15.584234 5.3800938 12.578 5.3046875 12 5.3046875 C 11.422 5.3046875 8.4157187 5.3810469 6.0117188 6.8730469 C 5.9327188 6.9210469 5.8457656 6.9433594 5.7597656 6.9433594 C 5.5997656 6.9433594 5.4425625 6.86475 5.3515625 6.71875 C 5.2115625 6.49375 5.2818594 6.1985938 5.5058594 6.0585938 C 6.9058594 5.1905937 8.4528906 4.7627812 9.7128906 4.5507812 C 9.6388906 4.3147813 9.5714062 4.1643437 9.5664062 4.1523438 C 9.4894063 3.9813438 9.3217188 3.8691406 9.1367188 3.8691406 z M 12 7.3046875 C 12.296 7.3046875 14.950594 7.3403125 16.933594 8.5703125 C 17.326594 8.8143125 17.777234 8.9453125 18.240234 8.9453125 C 18.633234 8.9453125 19.010656 8.8555 19.347656 8.6875 C 19.964656 10.2405 20.690828 12.686219 20.923828 15.199219 C 20.883828 15.143219 20.840922 15.089109 20.794922 15.037109 C 20.324922 14.498109 19.644687 14.191406 18.929688 14.191406 C 18.332687 14.191406 17.754078 14.405437 17.330078 14.773438 C 17.257078 14.832437 15.505 16.21875 12 16.21875 C 8.496 16.21875 6.7450313 14.834687 6.7070312 14.804688 C 6.2540312 14.407687 5.6742656 14.189453 5.0722656 14.189453 C 4.3612656 14.189453 3.6838438 14.494391 3.2148438 15.025391 C 3.1658438 15.080391 3.1201719 15.138266 3.0761719 15.197266 C 3.3091719 12.686266 4.0344375 10.235594 4.6484375 8.6835938 C 4.9864375 8.8525938 5.3657656 8.9433594 5.7597656 8.9433594 C 6.2217656 8.9433594 6.6724531 8.8143125 7.0644531 8.5703125 C 9.0494531 7.3393125 11.704 7.3046875 12 7.3046875 z M 8.890625 10.044922 C 7.966625 10.044922 7.2167969 10.901031 7.2167969 11.957031 C 7.2167969 13.013031 7.965625 13.869141 8.890625 13.869141 C 9.815625 13.869141 10.564453 13.013031 10.564453 11.957031 C 10.564453 10.900031 9.815625 10.044922 8.890625 10.044922 z M 15.109375 10.044922 C 14.185375 10.044922 13.435547 10.901031 13.435547 11.957031 C 13.435547 13.013031 14.184375 13.869141 15.109375 13.869141 C 16.034375 13.869141 16.783203 13.013031 16.783203 11.957031 C 16.783203 10.900031 16.033375 10.044922 15.109375 10.044922 z'; diff --git a/web/src/lib/components/shared-components/help-and-feedback-modal.svelte b/web/src/lib/components/shared-components/help-and-feedback-modal.svelte new file mode 100644 index 0000000000..1ae863e596 --- /dev/null +++ b/web/src/lib/components/shared-components/help-and-feedback-modal.svelte @@ -0,0 +1,131 @@ + + + + +

{$t('official_immich_resources')}

+ + {#if info.thirdPartyBugFeatureUrl || info.thirdPartySourceUrl || info.thirdPartyDocumentationUrl || info.thirdPartySupportUrl} +

{$t('third_party_resources')}

+

+ {$t('support_third_party_description')} +

+
+ {#if info.thirdPartyDocumentationUrl} + + {/if} + + {#if info.thirdPartySourceUrl} + + {/if} + + {#if info.thirdPartySupportUrl} + + {/if} + + {#if info.thirdPartyBugFeatureUrl} + + {/if} +
+ {/if} +
+
diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte index 28f8d7bd60..2f8d0e2574 100644 --- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte +++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte @@ -8,32 +8,45 @@ import { featureFlags } from '$lib/stores/server-config.store'; import { user } from '$lib/stores/user.store'; import { handleLogout } from '$lib/utils/auth'; - import { logout } from '@immich/sdk'; - import { mdiMagnify, mdiTrayArrowUp } from '@mdi/js'; + import { getAboutInfo, logout, type ServerAboutResponseDto } from '@immich/sdk'; + import { mdiHelpCircleOutline, mdiMagnify, mdiTrayArrowUp } from '@mdi/js'; import { t } from 'svelte-i18n'; import { fade } from 'svelte/transition'; - import { AppRoute } from '../../../constants'; - import ImmichLogo from '../immich-logo.svelte'; - import SearchBar from '../search-bar/search-bar.svelte'; + import { AppRoute } from '$lib/constants'; + import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte'; + import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte'; import ThemeButton from '../theme-button.svelte'; import UserAvatar from '../user-avatar.svelte'; import AccountInfoPanel from './account-info-panel.svelte'; + import HelpAndFeedbackModal from '$lib/components/shared-components/help-and-feedback-modal.svelte'; + import { onMount } from 'svelte'; export let showUploadButton = true; export let onUploadClick: () => void; let shouldShowAccountInfo = false; let shouldShowAccountInfoPanel = false; + let shouldShowHelpPanel = false; let innerWidth: number; const onLogout = async () => { const { redirectUri } = await logout(); await handleLogout(redirectUri); }; + + let aboutInfo: ServerAboutResponseDto; + + onMount(async () => { + aboutInfo = await getAboutInfo(); + }); +{#if shouldShowHelpPanel} + (shouldShowHelpPanel = false)} info={aboutInfo} /> +{/if} +
-
+
{#if $featureFlags.search} +
(shouldShowHelpPanel = false), + }} + > + (shouldShowHelpPanel = !shouldShowHelpPanel)} + padding="1" + /> +
+ {#if !$page.url.pathname.includes('/admin') && showUploadButton}