diff --git a/src/app/apps/page.tsx b/src/app/apps/page.tsx
index 5fe23c3..9d2144c 100644
--- a/src/app/apps/page.tsx
+++ b/src/app/apps/page.tsx
@@ -3,6 +3,7 @@
import Link from "next/link";
import {
type FormEvent,
+ type ReactNode,
useCallback,
useEffect,
useMemo,
@@ -349,6 +350,104 @@ function getTemplateVisual(template: AppTemplate | null) {
};
}
+function normalizeExternalUrl(value?: string) {
+ const trimmed = value?.trim();
+ if (!trimmed) return "";
+
+ if (/^https?:\/\//i.test(trimmed)) {
+ return trimmed;
+ }
+
+ return `https://${trimmed}`;
+}
+
+function getTemplateLinks(template: AppTemplate) {
+ return {
+ github: normalizeExternalUrl(template.presentation?.github),
+ dockerHub: normalizeExternalUrl(template.presentation?.["hub-docker"]),
+ };
+}
+
+function SimpleIcon({
+ src,
+ label,
+ size,
+}: {
+ src: string;
+ label: string;
+ size: number;
+}) {
+ return (
+ // eslint-disable-next-line @next/next/no-img-element
+
+ );
+}
+
+function MaskIcon({
+ src,
+ size,
+}: {
+ src: string;
+ size: number;
+}) {
+ return (
+
+ );
+}
+
+function TemplateSourceLink({
+ href,
+ label,
+ children,
+}: {
+ href: string;
+ label: string;
+ children: ReactNode;
+}) {
+ if (!href) return null;
+
+ return (
+ e.stopPropagation()}
+ style={{
+ width: 26,
+ height: 26,
+ borderRadius: 7,
+ border: "1px solid var(--border)",
+ background: "var(--bg-input)",
+ display: "inline-grid",
+ placeItems: "center",
+ color: "var(--text-muted)",
+ textDecoration: "none",
+ flexShrink: 0,
+ }}
+ >
+ {children}
+
+ );
+}
+
function AppIconBadge({
template,
size,
@@ -2336,6 +2435,7 @@ export default function AppsPage() {
>
{filtered.map((app) => {
const visual = getTemplateVisual(app);
+ const sourceLinks = getTemplateLinks(app);
const installCount = installCounts.get(app.id) ?? 0;
const visibleTags = app.tags.slice(0, 4);
const hiddenTagCount = Math.max(
@@ -2512,11 +2612,34 @@ export default function AppsPage() {
gap: 8,
}}
>
-
- {visual.installs.toLocaleString()} installs
-
+
+
+
+
+
+
+
{
icon: "icon.png",
color: "#f59e0b",
installs: 42,
+ github: "https://github.com/henrygd/beszel",
+ "hub-docker": "https://hub.docker.com/r/henrygd/beszel",
},
},
"beszel",
@@ -57,6 +59,8 @@ test("generic app template keeps its explicit category", () => {
icon: "https://raw.githubusercontent.com/DoktainerApp/templates/main/templates/beszel/icon.png",
color: "#f59e0b",
installs: 42,
+ github: "https://github.com/henrygd/beszel",
+ "hub-docker": "https://hub.docker.com/r/henrygd/beszel",
});
});