show Dockhand's own mark on its own container
This commit is contained in:
3 files changed
+78
-2
No files matched your search
@@ -4,7 +4,7 @@
|
||||
import { appSettings } from '$lib/stores/settings';
|
||||
import { selfhstMatcher, ensureSelfhstMatcher } from '$lib/stores/selfhst-refs';
|
||||
import { selfhstIcons, requestSelfhst } from '$lib/stores/selfhst-icons';
|
||||
import { isSelfhstIcon, selfhstRef, isCustomIcon, getStackIconComponent } from '$lib/utils/icons';
|
||||
import { isSelfhstIcon, selfhstRef, isCustomIcon, getStackIconComponent, isDockhandImage } from '$lib/utils/icons';
|
||||
|
||||
interface Props {
|
||||
image: string;
|
||||
@@ -62,6 +62,10 @@
|
||||
|
||||
// An explicit override wins over auto-matching (and the app-logo toggle).
|
||||
const hasOverride = $derived(!!override);
|
||||
|
||||
// Dockhand's own container always shows the app mark (unless the user overrode it),
|
||||
// regardless of the app-logo toggle - it is our own branding, not a selfh.st match.
|
||||
const isSelf = $derived(!hasOverride && isDockhandImage(image));
|
||||
const overrideSelfhst = $derived(selfhstRef(override));
|
||||
const overrideCustom = $derived(isCustomIcon(override));
|
||||
const customIconName = $derived(overrideKey ?? name);
|
||||
@@ -100,7 +104,9 @@
|
||||
const resolvedSelfhst = $derived(activeSelfhst ? $selfhstIcons[activeSelfhst] : undefined);
|
||||
</script>
|
||||
|
||||
{#if activeSelfhst && resolvedSelfhst}
|
||||
{#if isSelf}
|
||||
<img src="/dockhand-mark.svg" alt="Dockhand" class="{className} object-contain shrink-0" />
|
||||
{:else if activeSelfhst && resolvedSelfhst}
|
||||
<img src={resolvedSelfhst} alt="" class="{className} object-contain shrink-0" />
|
||||
{:else if activeSelfhst && resolvedSelfhst === undefined}
|
||||
<!-- batch in flight: hold the space so the row doesn't jump -->
|
||||
|
||||
@@ -56,6 +56,25 @@ export function selfhstRef(icon: string | null | undefined): string | null {
|
||||
return isSelfhstIcon(icon) ? icon!.slice('selfhst:'.length) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an image is Dockhand's own image, so its container shows the app mark.
|
||||
* Matches the `dockhand` repo under the `fnsys`/`finsys` org from any registry,
|
||||
* ignoring the tag/digest (e.g. `fnsys/dockhand:v1.0.47`,
|
||||
* `docker.io/finsys/dockhand`, `registry.bor6.pl/dockhand@sha256:...`).
|
||||
*/
|
||||
export function isDockhandImage(image: string | null | undefined): boolean {
|
||||
if (!image) return false;
|
||||
const path = image.split('@')[0].split('/'); // drop digest, split path
|
||||
// The tag lives only in the LAST segment (after the last '/'); a registry
|
||||
// host may carry a ':port' earlier, so strip the tag from the last segment only.
|
||||
const name = path[path.length - 1].split(':')[0];
|
||||
if (name !== 'dockhand') return false;
|
||||
const org = path.length >= 2 ? path[path.length - 2] : '';
|
||||
// The official org, or a private-registry host (`registry.example/dockhand`).
|
||||
// A bare or unqualified `<org>/dockhand` is NOT matched - too broad.
|
||||
return org === 'fnsys' || org === 'finsys' || org.includes('.') || org.includes(':');
|
||||
}
|
||||
|
||||
export { iconMap };
|
||||
|
||||
// --- Stack icon set (SEPARATE from env; app/service-oriented) --------------------
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* isDockhandImage - whether a container image is Dockhand's own, so its
|
||||
* container renders the app mark instead of an auto-matched / generic icon.
|
||||
*/
|
||||
|
||||
import { describe, test, expect } from 'bun:test';
|
||||
import { isDockhandImage } from '../src/lib/utils/icons';
|
||||
|
||||
describe('isDockhandImage - matches Dockhand images', () => {
|
||||
test('official org, with tag', () => {
|
||||
expect(isDockhandImage('fnsys/dockhand:v1.0.47')).toBe(true);
|
||||
});
|
||||
test('official org, no tag', () => {
|
||||
expect(isDockhandImage('fnsys/dockhand')).toBe(true);
|
||||
});
|
||||
test('finsys spelling variant', () => {
|
||||
expect(isDockhandImage('finsys/dockhand:latest')).toBe(true);
|
||||
});
|
||||
test('docker.io fully qualified', () => {
|
||||
expect(isDockhandImage('docker.io/fnsys/dockhand:v1.0.47')).toBe(true);
|
||||
});
|
||||
test('private registry host / dockhand', () => {
|
||||
expect(isDockhandImage('registry.bor6.pl/dockhand:0a9de97e')).toBe(true);
|
||||
});
|
||||
test('private registry with port', () => {
|
||||
expect(isDockhandImage('registry.bor6.pl:5000/dockhand')).toBe(true);
|
||||
});
|
||||
test('digest pinned', () => {
|
||||
expect(isDockhandImage('fnsys/dockhand@sha256:abc123')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDockhandImage - does NOT match', () => {
|
||||
test('unrelated image', () => {
|
||||
expect(isDockhandImage('postgres:15.2')).toBe(false);
|
||||
});
|
||||
test('a different app named dockhand under someone else\'s Docker Hub org', () => {
|
||||
// A bare `<org>/dockhand` on Docker Hub (org has no dot/port) is too ambiguous
|
||||
// to claim as ours - only the official org or a registry host qualifies.
|
||||
expect(isDockhandImage('someuser/dockhand')).toBe(false);
|
||||
});
|
||||
test('substring in a longer name is not a match', () => {
|
||||
expect(isDockhandImage('fnsys/dockhand-agent')).toBe(false);
|
||||
expect(isDockhandImage('fnsys/not-dockhand')).toBe(false);
|
||||
});
|
||||
test('empty / null / undefined', () => {
|
||||
expect(isDockhandImage('')).toBe(false);
|
||||
expect(isDockhandImage(null)).toBe(false);
|
||||
expect(isDockhandImage(undefined)).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user