feat(gitea): homelab landing as the Gitea home and dashboard
The home page, signed-in dashboard and navbar render the homelab landing template (service panels with health badges) instead of Gitea's stock home.
This commit is contained in:
182
infra/gitea/custom/templates/base/head_navbar.tmpl
Normal file
182
infra/gitea/custom/templates/base/head_navbar.tmpl
Normal file
@@ -0,0 +1,182 @@
|
||||
{{/* fhirworx copy of Gitea 1.25.4 templates/base/head_navbar.tmpl.
|
||||
Home (custom/extra_links) is the first link, before Explore.
|
||||
Re-diff against the new version whenever the Gitea image changes. */}}
|
||||
<nav id="navbar" aria-label="{{ctx.Locale.Tr "aria.navbar"}}">
|
||||
<div class="navbar-left">
|
||||
<!-- the logo -->
|
||||
<a class="item" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{if .IsSigned}}{{ctx.Locale.Tr "dashboard"}}{{else}}{{ctx.Locale.Tr "home"}}{{end}}">
|
||||
<img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
|
||||
</a>
|
||||
|
||||
<!-- mobile right menu, it must be here because in mobile view, each item is a flex column, the first item is a full row column -->
|
||||
<div class="ui secondary menu navbar-mobile-right only-mobile">
|
||||
{{template "base/head_navbar_icons" dict "PageGlobalData" .PageGlobalData}}
|
||||
<button class="item ui icon mini button tw-m-0" id="navbar-expand-toggle" aria-label="{{ctx.Locale.Tr "home.nav_menu"}}">{{svg "octicon-three-bars"}}</button>
|
||||
</div>
|
||||
|
||||
<!-- navbar links non-mobile. Home is first. -->
|
||||
{{template "custom/extra_links" .}}
|
||||
{{if and .IsSigned .MustChangePassword}}
|
||||
{{/* No links */}}
|
||||
{{else if .IsSigned}}
|
||||
{{if not ctx.Consts.RepoUnitTypeIssues.UnitGlobalDisabled}}
|
||||
<a class="item{{if .PageIsIssues}} active{{end}}" href="{{AppSubUrl}}/issues">{{ctx.Locale.Tr "issues"}}</a>
|
||||
{{end}}
|
||||
{{if not ctx.Consts.RepoUnitTypePullRequests.UnitGlobalDisabled}}
|
||||
<a class="item{{if .PageIsPulls}} active{{end}}" href="{{AppSubUrl}}/pulls">{{ctx.Locale.Tr "pull_requests"}}</a>
|
||||
{{end}}
|
||||
{{if not (and ctx.Consts.RepoUnitTypeIssues.UnitGlobalDisabled ctx.Consts.RepoUnitTypePullRequests.UnitGlobalDisabled)}}
|
||||
{{if .ShowMilestonesDashboardPage}}
|
||||
<a class="item{{if .PageIsMilestonesDashboard}} active{{end}}" href="{{AppSubUrl}}/milestones">{{ctx.Locale.Tr "milestones"}}</a>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a>
|
||||
{{else if .IsLandingPageOrganizations}}
|
||||
<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/organizations">{{ctx.Locale.Tr "explore"}}</a>
|
||||
{{else}}
|
||||
<a class="item{{if .PageIsExplore}} active{{end}}" href="{{AppSubUrl}}/explore/repos">{{ctx.Locale.Tr "explore"}}</a>
|
||||
{{end}}
|
||||
|
||||
{{if not .IsSigned}}
|
||||
<a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com">{{ctx.Locale.Tr "help"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- the full dropdown menus -->
|
||||
<div class="navbar-right">
|
||||
{{if and .IsSigned .MustChangePassword}}
|
||||
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
|
||||
<span class="text">
|
||||
{{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
|
||||
<span class="only-mobile">{{.SignedUser.Name}}</span>
|
||||
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
|
||||
</span>
|
||||
<div class="menu user-menu">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
<a class="item link-action" href data-url="{{AppSubUrl}}/user/logout">
|
||||
{{svg "octicon-sign-out"}}
|
||||
{{ctx.Locale.Tr "sign_out"}}
|
||||
</a>
|
||||
</div><!-- end content avatar menu -->
|
||||
</div><!-- end dropdown avatar menu -->
|
||||
{{else if .IsSigned}}
|
||||
{{template "base/head_navbar_icons" dict "ItemExtraClass" "not-mobile" "PageGlobalData" .PageGlobalData}}
|
||||
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "create_new"}}">
|
||||
<span class="text">
|
||||
{{svg "octicon-plus"}}
|
||||
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
|
||||
<span class="only-mobile">{{ctx.Locale.Tr "create_new"}}</span>
|
||||
</span>
|
||||
<div class="menu">
|
||||
<a class="item" href="{{AppSubUrl}}/repo/create">
|
||||
{{svg "octicon-plus"}} {{ctx.Locale.Tr "new_repo"}}
|
||||
</a>
|
||||
{{if not .DisableMigrations}}
|
||||
<a class="item" href="{{AppSubUrl}}/repo/migrate">
|
||||
{{svg "octicon-repo-push"}} {{ctx.Locale.Tr "new_migrate"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .SignedUser.CanCreateOrganization}}
|
||||
<a class="item" href="{{AppSubUrl}}/org/create">
|
||||
{{svg "octicon-organization"}} {{ctx.Locale.Tr "new_org"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div><!-- end content create new menu -->
|
||||
</div><!-- end dropdown menu create new -->
|
||||
|
||||
<div class="ui dropdown jump item" data-tooltip-content="{{ctx.Locale.Tr "user_profile_and_more"}}">
|
||||
<span class="text">
|
||||
{{ctx.AvatarUtils.Avatar .SignedUser 24 "tw-mr-1"}}
|
||||
<span class="only-mobile">{{.SignedUser.Name}}</span>
|
||||
<span class="not-mobile">{{svg "octicon-triangle-down"}}</span>
|
||||
</span>
|
||||
<div class="menu user-menu">
|
||||
<div class="header">
|
||||
{{ctx.Locale.Tr "signed_in_as"}} <strong>{{.SignedUser.Name}}</strong>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
<a class="item" href="{{.SignedUser.HomeLink}}">
|
||||
{{svg "octicon-person"}}
|
||||
{{ctx.Locale.Tr "your_profile"}}
|
||||
</a>
|
||||
{{if not .DisableStars}}
|
||||
<a class="item" href="{{.SignedUser.HomeLink}}?tab=stars">
|
||||
{{svg "octicon-star"}}
|
||||
{{ctx.Locale.Tr "your_starred"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="item" href="{{AppSubUrl}}/notifications/subscriptions">
|
||||
{{svg "octicon-bell"}}
|
||||
{{ctx.Locale.Tr "notification.subscriptions"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsUserSettings}}active {{end}}item" href="{{AppSubUrl}}/user/settings">
|
||||
{{svg "octicon-tools"}}
|
||||
{{ctx.Locale.Tr "your_settings"}}
|
||||
</a>
|
||||
<a class="item" target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com">
|
||||
{{svg "octicon-question"}}
|
||||
{{ctx.Locale.Tr "help"}}
|
||||
</a>
|
||||
{{if .IsAdmin}}
|
||||
<div class="divider"></div>
|
||||
<a class="{{if .PageIsAdmin}}active {{end}}item" href="{{AppSubUrl}}/-/admin">
|
||||
{{svg "octicon-server"}}
|
||||
{{ctx.Locale.Tr "admin_panel"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<div class="divider"></div>
|
||||
<a class="item link-action" href data-url="{{AppSubUrl}}/user/logout">
|
||||
{{svg "octicon-sign-out"}}
|
||||
{{ctx.Locale.Tr "sign_out"}}
|
||||
</a>
|
||||
</div><!-- end content avatar menu -->
|
||||
</div><!-- end dropdown avatar menu -->
|
||||
{{else}}
|
||||
{{if .ShowRegistrationButton}}
|
||||
<a class="item{{if .PageIsSignUp}} active{{end}}" href="{{AppSubUrl}}/user/sign_up">
|
||||
{{svg "octicon-person"}}
|
||||
<span class="tw-ml-1">{{ctx.Locale.Tr "register"}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="item{{if .PageIsSignIn}} active{{end}}" rel="nofollow" href="{{AppSubUrl}}/user/login{{if not .PageIsSignIn}}?redirect_to={{.CurrentURL}}{{end}}">
|
||||
{{svg "octicon-sign-in"}}
|
||||
<span class="tw-ml-1">{{ctx.Locale.Tr "sign_in"}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
</div><!-- end full right menu -->
|
||||
|
||||
{{$activeStopwatch := and .PageGlobalData (call .PageGlobalData.GetActiveStopwatch)}}
|
||||
{{if $activeStopwatch}}
|
||||
<div class="active-stopwatch-popup tippy-target">
|
||||
<div class="tw-flex tw-items-center tw-gap-2 tw-p-3">
|
||||
<a class="stopwatch-link tw-flex tw-items-center tw-gap-2 muted" href="{{$activeStopwatch.IssueLink}}">
|
||||
{{svg "octicon-issue-opened" 16}}
|
||||
<span class="stopwatch-issue">{{$activeStopwatch.RepoSlug}}#{{$activeStopwatch.IssueIndex}}</span>
|
||||
</a>
|
||||
<div class="tw-flex tw-gap-1">
|
||||
<form class="stopwatch-commit form-fetch-action" method="post" action="{{$activeStopwatch.IssueLink}}/times/stopwatch/stop">
|
||||
{{.CsrfTokenHtml}}
|
||||
<button
|
||||
type="submit"
|
||||
class="ui button mini compact basic icon tw-mr-0"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.issues.stop_tracking"}}"
|
||||
>{{svg "octicon-square-fill"}}</button>
|
||||
</form>
|
||||
<form class="stopwatch-cancel form-fetch-action" method="post" action="{{$activeStopwatch.IssueLink}}/times/stopwatch/cancel">
|
||||
{{.CsrfTokenHtml}}
|
||||
<button
|
||||
type="submit"
|
||||
class="ui button mini compact basic icon tw-mr-0"
|
||||
data-tooltip-content="{{ctx.Locale.Tr "repo.issues.cancel_tracking"}}"
|
||||
>{{svg "octicon-trash"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</nav>
|
||||
1
infra/gitea/custom/templates/custom/extra_links.tmpl
Normal file
1
infra/gitea/custom/templates/custom/extra_links.tmpl
Normal file
@@ -0,0 +1 @@
|
||||
<a class="item{{if or .PageIsHome .PageIsDashboard}} active{{end}}" href="{{AppSubUrl}}/">Home</a>
|
||||
@@ -1,423 +1,3 @@
|
||||
{{template "base/head" .}}
|
||||
<style>
|
||||
/* HTI-5 dashboard tokens — defined locally so the inline template is self-contained.
|
||||
gitea.css maps Gitea's --color-* vars to hti5 tokens at load time. */
|
||||
.homelab-dashboard {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 1.5rem;
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
}
|
||||
|
||||
/* Rule lines */
|
||||
.homelab-dashboard .rule-line {
|
||||
border: none;
|
||||
border-top: 3px solid var(--color-text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.homelab-dashboard .rule-line-thin {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Masthead */
|
||||
.homelab-dashboard .masthead {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-logo {
|
||||
width: clamp(48px, 6vw, 80px);
|
||||
height: clamp(48px, 6vw, 80px);
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.homelab-dashboard .masthead-row h1 {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: clamp(2.5rem, 5vw, 3.5rem) !important;
|
||||
font-weight: 800 !important;
|
||||
color: var(--color-text) !important;
|
||||
letter-spacing: -0.02em !important;
|
||||
line-height: 1.1 !important;
|
||||
border: none !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.homelab-dashboard .masthead-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
align-self: flex-end;
|
||||
padding-bottom: 0.35rem;
|
||||
}
|
||||
.homelab-dashboard .tagline {
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-text-light);
|
||||
max-width: 65ch;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Section header */
|
||||
.homelab-dashboard .section-header {
|
||||
margin-bottom: 1.5rem;
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.homelab-dashboard .section-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.4rem;
|
||||
display: block;
|
||||
}
|
||||
.homelab-dashboard .section-header h2 {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: 1.75rem !important;
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
color: var(--color-text) !important;
|
||||
line-height: 1.2 !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.homelab-dashboard .grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* Tiles */
|
||||
.homelab-dashboard .tile {
|
||||
background: var(--color-box-body);
|
||||
border: 1px solid var(--color-border) !important;
|
||||
border-top: 3px solid var(--color-text) !important;
|
||||
padding: 1.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none !important;
|
||||
display: block;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.homelab-dashboard .tile:hover {
|
||||
border-color: var(--color-primary) !important;
|
||||
background: var(--color-body);
|
||||
}
|
||||
.homelab-dashboard .tile .icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
.homelab-dashboard .tile.storage .icon,
|
||||
.homelab-dashboard .tile.database .icon { color: var(--color-warning); }
|
||||
.homelab-dashboard .tile.git .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.ci .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.notebook .icon,
|
||||
.homelab-dashboard .tile.research .icon { color: var(--color-text); }
|
||||
.homelab-dashboard .tile.catalog .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.query .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.observability .icon { color: var(--color-warning); }
|
||||
.homelab-dashboard .tile.metrics .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.logs .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.proxy .icon { color: var(--color-text); }
|
||||
|
||||
/* Tile eyebrow — mono metadata label */
|
||||
.homelab-dashboard .tile-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Tile title — Playfair Display */
|
||||
.homelab-dashboard .tile-title {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: 1.25rem !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 1.2 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
color: var(--color-text) !important;
|
||||
margin: 0.25rem 0 0.5rem !important;
|
||||
border: none !important;
|
||||
text-transform: none !important;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.homelab-dashboard .tile:hover .tile-title {
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
/* Tile description — Source Serif 4 */
|
||||
.homelab-dashboard .tile-desc {
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text-light);
|
||||
line-height: 1.55;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Tile port — mono metadata */
|
||||
.homelab-dashboard .tile-port {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.65rem;
|
||||
color: var(--color-text-light);
|
||||
opacity: 0.7;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Status dot */
|
||||
.homelab-dashboard .status-dot {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: var(--color-success);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Badge */
|
||||
.homelab-dashboard .tile-badge {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 1.25rem;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
color: var(--color-primary-foreground, #F7F5F0);
|
||||
font-size: 0.6rem;
|
||||
padding: 3px 7px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.homelab-dashboard .tile-badge.new { background: var(--color-warning); }
|
||||
.homelab-dashboard .tile-badge.gpu { background: var(--color-primary); }
|
||||
.homelab-dashboard .tile-badge.data { background: var(--color-primary); }
|
||||
|
||||
/* Bottom accent */
|
||||
.homelab-dashboard .stripe {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.homelab-dashboard .ftr {
|
||||
padding-top: 1rem;
|
||||
border-top: 3px solid var(--color-text);
|
||||
color: var(--color-text-light);
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.homelab-dashboard .ftr span { color: var(--color-warning); }
|
||||
</style>
|
||||
<div class="homelab-dashboard">
|
||||
<div class="masthead">
|
||||
<div class="rule-line"></div>
|
||||
<div class="masthead-row">
|
||||
<div class="masthead-brand">
|
||||
<img src="/assets/img/logo.svg" alt="fhirworx" class="masthead-logo">
|
||||
<h1>Homelab</h1>
|
||||
</div>
|
||||
<span class="masthead-eyebrow">git.fhirworx.io</span>
|
||||
</div>
|
||||
<p class="tagline">
|
||||
Self-hosted infrastructure for FHIR data engineering — version control, CI/CD,
|
||||
notebooks, and the full lakehouse stack.
|
||||
</p>
|
||||
<div class="rule-line-thin"></div>
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
<span class="section-eyebrow">Services</span>
|
||||
<h2>Infrastructure</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🔗</span>
|
||||
<div class="tile-eyebrow">Networking</div>
|
||||
<div class="tile-title">Traefik</div>
|
||||
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">💾</span>
|
||||
<div class="tile-eyebrow">Storage</div>
|
||||
<div class="tile-title">RustFS</div>
|
||||
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="git" class="tile git">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🐙</span>
|
||||
<div class="tile-eyebrow">Version Control</div>
|
||||
<div class="tile-title">Gitea</div>
|
||||
<p class="tile-desc">Self-hosted Git service with packages and CI</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="notebooks" target="_blank" class="tile notebook">
|
||||
<span class="tile-badge gpu">GPU</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📑</span>
|
||||
<div class="tile-eyebrow">Analysis</div>
|
||||
<div class="tile-title">Notebooks</div>
|
||||
<p class="tile-desc">Marimo reactive notebooks with CUDA acceleration</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="zotero" target="_blank" class="tile research">
|
||||
<span class="tile-badge gpu">GPU</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📚</span>
|
||||
<div class="tile-eyebrow">Research</div>
|
||||
<div class="tile-title">Zotero</div>
|
||||
<p class="tile-desc">Research reference manager in the cloud</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="docs" target="_blank" class="tile research">
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📖</span>
|
||||
<div class="tile-eyebrow">Documentation</div>
|
||||
<div class="tile-title">Docs</div>
|
||||
<p class="tile-desc">API reference and CMS bibliography browser</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="nessie" data-path="/api/v2/config" target="_blank" class="tile catalog">
|
||||
<span class="tile-badge data">Data</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🌳</span>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Nessie</div>
|
||||
<p class="tile-desc">Git-like versioned data catalog for Apache Iceberg</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="trino" target="_blank" class="tile query">
|
||||
<span class="tile-badge data">Data</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">🔍</span>
|
||||
<div class="tile-eyebrow">Query</div>
|
||||
<div class="tile-title">Trino</div>
|
||||
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">⭐</span>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Polaris</div>
|
||||
<p class="tile-desc">Apache Iceberg catalog with governance and Unity-style access</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📊</span>
|
||||
<div class="tile-eyebrow">Observability</div>
|
||||
<div class="tile-title">Grafana</div>
|
||||
<p class="tile-desc">Observability dashboards for metrics, logs, and traces</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="tempo" target="_blank" class="tile metrics">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">👁</span>
|
||||
<div class="tile-eyebrow">Tracing</div>
|
||||
<div class="tile-title">Tempo</div>
|
||||
<p class="tile-desc">Distributed tracing for microservices</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📈</span>
|
||||
<div class="tile-eyebrow">Metrics</div>
|
||||
<div class="tile-title">Prometheus</div>
|
||||
<p class="tile-desc">Metrics collection and alerting</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="loki" data-path="/ready" target="_blank" class="tile database">
|
||||
<span class="status-dot"></span>
|
||||
<span class="icon">📋</span>
|
||||
<div class="tile-eyebrow">Logs</div>
|
||||
<div class="tile-title">Loki</div>
|
||||
<p class="tile-desc">Log aggregation system by Grafana</p>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="ftr">
|
||||
<span>fhirworx.io</span> — self-hosted data lakehouse — © 2025
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
var parts = location.hostname.split('.');
|
||||
var base = parts.length > 2 ? parts.slice(1).join('.') : location.hostname;
|
||||
document.querySelectorAll('.homelab-dashboard [data-subdomain]').forEach(function(el) {
|
||||
var sub = el.dataset.subdomain;
|
||||
var path = el.dataset.path || '';
|
||||
var fqdn = sub + '.' + base;
|
||||
el.href = location.protocol + '//' + fqdn + path;
|
||||
el.target = '_blank';
|
||||
var port = el.querySelector('.tile-port');
|
||||
if (port) port.textContent = fqdn;
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{{template "homelab/landing" .}}
|
||||
{{template "base/footer" .}}
|
||||
|
||||
509
infra/gitea/custom/templates/homelab/landing.tmpl
Normal file
509
infra/gitea/custom/templates/homelab/landing.tmpl
Normal file
@@ -0,0 +1,509 @@
|
||||
<style>
|
||||
/* HTI-5 dashboard tokens — defined locally so the inline template is self-contained.
|
||||
gitea.css maps Gitea's --color-* vars to hti5 tokens at load time. */
|
||||
.homelab-dashboard {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 3rem 1.5rem;
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
}
|
||||
|
||||
/* Rule lines */
|
||||
.homelab-dashboard .rule-line {
|
||||
border: none;
|
||||
border-top: 3px solid var(--color-text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.homelab-dashboard .rule-line-thin {
|
||||
border: none;
|
||||
border-top: 1px solid var(--color-border);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Masthead */
|
||||
.homelab-dashboard .masthead {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
.homelab-dashboard .masthead-logo {
|
||||
width: clamp(48px, 6vw, 80px);
|
||||
height: clamp(48px, 6vw, 80px);
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.homelab-dashboard .masthead-row h1 {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: clamp(2.5rem, 5vw, 3.5rem) !important;
|
||||
font-weight: 800 !important;
|
||||
color: var(--color-text) !important;
|
||||
letter-spacing: -0.02em !important;
|
||||
line-height: 1.1 !important;
|
||||
border: none !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
.homelab-dashboard .masthead-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
align-self: flex-end;
|
||||
padding-bottom: 0.35rem;
|
||||
}
|
||||
.homelab-dashboard .tagline {
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
font-size: 1.125rem;
|
||||
color: var(--color-text-light);
|
||||
max-width: 65ch;
|
||||
line-height: 1.65;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
/* Section header */
|
||||
.homelab-dashboard .section-header {
|
||||
margin-bottom: 1.5rem;
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
.homelab-dashboard .section-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.4rem;
|
||||
display: block;
|
||||
}
|
||||
.homelab-dashboard .section-header h2 {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: 1.75rem !important;
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
color: var(--color-text) !important;
|
||||
line-height: 1.2 !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Grid */
|
||||
.homelab-dashboard .grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
/* Tiles */
|
||||
.homelab-dashboard .tile {
|
||||
background: var(--color-box-body);
|
||||
border: 1px solid var(--color-border) !important;
|
||||
border-top: 3px solid var(--color-text) !important;
|
||||
padding: 1.5rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
transition: border-color 0.15s ease, background 0.15s ease;
|
||||
cursor: pointer;
|
||||
text-decoration: none !important;
|
||||
display: block;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
.homelab-dashboard .tile:hover {
|
||||
border-color: var(--color-primary) !important;
|
||||
background: var(--color-body);
|
||||
}
|
||||
.homelab-dashboard .tile .icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
display: block;
|
||||
line-height: 1;
|
||||
}
|
||||
.homelab-dashboard .tile.storage .icon,
|
||||
.homelab-dashboard .tile.database .icon { color: var(--color-warning); }
|
||||
.homelab-dashboard .tile.git .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.ci .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.notebook .icon,
|
||||
.homelab-dashboard .tile.research .icon { color: var(--color-text); }
|
||||
.homelab-dashboard .tile.catalog .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.query .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.observability .icon { color: var(--color-warning); }
|
||||
.homelab-dashboard .tile.metrics .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.logs .icon { color: var(--color-primary); }
|
||||
.homelab-dashboard .tile.proxy .icon { color: var(--color-text); }
|
||||
|
||||
/* Tile eyebrow — mono metadata label */
|
||||
.homelab-dashboard .tile-eyebrow {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.12em;
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Tile title — Playfair Display */
|
||||
.homelab-dashboard .tile-title {
|
||||
font-family: "Playfair Display", Georgia, serif !important;
|
||||
font-size: 1.25rem !important;
|
||||
font-weight: 700 !important;
|
||||
line-height: 1.2 !important;
|
||||
letter-spacing: -0.01em !important;
|
||||
color: var(--color-text) !important;
|
||||
margin: 0.25rem 0 0.5rem !important;
|
||||
border: none !important;
|
||||
text-transform: none !important;
|
||||
transition: color 0.15s ease;
|
||||
}
|
||||
.homelab-dashboard .tile:hover .tile-title {
|
||||
color: var(--color-primary) !important;
|
||||
}
|
||||
|
||||
/* Tile description — Source Serif 4 */
|
||||
.homelab-dashboard .tile-desc {
|
||||
font-family: "Source Serif 4", Georgia, serif;
|
||||
font-size: 0.95rem;
|
||||
color: var(--color-text-light);
|
||||
line-height: 1.55;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Tile port — mono metadata */
|
||||
.homelab-dashboard .tile-port {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.65rem;
|
||||
color: var(--color-text-light);
|
||||
opacity: 0.7;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Health, inside the tile. Same shape as the corwins.media status badge:
|
||||
a label, a status word, and a left edge that carries the state.
|
||||
The state colours come after the border so they win. */
|
||||
.homelab-dashboard .health {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 0 0.85rem;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-left-width: 4px;
|
||||
background: var(--color-body);
|
||||
}
|
||||
.homelab-dashboard .health-k {
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
.homelab-dashboard .health-v {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.homelab-dashboard .health-ok { border-left-color: var(--color-green, var(--color-success)) !important; }
|
||||
.homelab-dashboard .health-warn { border-left-color: var(--color-yellow, var(--color-warning)) !important; }
|
||||
.homelab-dashboard .health-bad { border-left-color: var(--color-red, var(--color-error)) !important; }
|
||||
.homelab-dashboard .health-unknown { border-left-color: var(--color-text-light) !important; }
|
||||
|
||||
/* Badge */
|
||||
.homelab-dashboard .tile-badge {
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 1.25rem;
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
color: var(--color-primary-foreground, #F7F5F0);
|
||||
font-size: 0.6rem;
|
||||
padding: 3px 7px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.homelab-dashboard .tile-badge.new { background: var(--color-warning); }
|
||||
.homelab-dashboard .tile-badge.gpu { background: var(--color-primary); }
|
||||
.homelab-dashboard .tile-badge.data { background: var(--color-primary); }
|
||||
|
||||
/* Bottom accent */
|
||||
.homelab-dashboard .stripe {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 2px;
|
||||
background: var(--color-border);
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.homelab-dashboard .ftr {
|
||||
padding-top: 1rem;
|
||||
border-top: 3px solid var(--color-text);
|
||||
color: var(--color-text-light);
|
||||
font-family: "JetBrains Mono", "Fira Code", monospace;
|
||||
font-size: 0.65rem;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.homelab-dashboard .ftr span { color: var(--color-warning); }
|
||||
</style>
|
||||
<div class="homelab-dashboard">
|
||||
<div class="masthead">
|
||||
<div class="rule-line"></div>
|
||||
<div class="masthead-row">
|
||||
<div class="masthead-brand">
|
||||
<img src="/assets/img/logo.svg" alt="fhirworx" class="masthead-logo">
|
||||
<h1>Homelab</h1>
|
||||
</div>
|
||||
<span class="masthead-eyebrow">git.fhirworx.io</span>
|
||||
</div>
|
||||
<p class="tagline">
|
||||
Self-hosted infrastructure for FHIR data engineering — version control, CI/CD,
|
||||
notebooks, and the full lakehouse stack.
|
||||
</p>
|
||||
<div class="rule-line-thin"></div>
|
||||
</div>
|
||||
|
||||
<div class="section-header">
|
||||
<span class="section-eyebrow">Services</span>
|
||||
<h2>Infrastructure</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid" data-health-origin="https://fhirworx.io">
|
||||
<a data-subdomain="llm" target="_blank" class="tile notebook">
|
||||
<span class="tile-badge gpu">GPU</span>
|
||||
<span class="icon">💬</span>
|
||||
<div class="tile-eyebrow">Chat</div>
|
||||
<div class="tile-title">Comment Chat</div>
|
||||
<p class="tile-desc">RAG chat over indexed CMS rulemaking comments, with cited sources</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="bcda" target="_blank" class="tile notebook">
|
||||
<span class="icon">📄</span>
|
||||
<div class="tile-eyebrow">Claims</div>
|
||||
<div class="tile-title">BCDA Manager</div>
|
||||
<p class="tile-desc">Beneficiary Claims Data API v3 — exports, store, load, crosswalk</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="lead" target="_blank" class="tile notebook">
|
||||
<span class="icon">📈</span>
|
||||
<div class="tile-eyebrow">LEAD</div>
|
||||
<div class="tile-title">Public mirror</div>
|
||||
<p class="tile-desc">Read-only explorer over the public-data DuckDB mirror</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="reach" target="_blank" class="tile research">
|
||||
<span class="icon">📊</span>
|
||||
<div class="tile-eyebrow">REACH</div>
|
||||
<div class="tile-title">Public mirror</div>
|
||||
<p class="tile-desc">Read-only explorer over the public-data DuckDB mirror</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="traefik" target="_blank" class="tile proxy">
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="icon">🔗</span>
|
||||
<div class="tile-eyebrow">Networking</div>
|
||||
<div class="tile-title">Traefik</div>
|
||||
<p class="tile-desc">Cloud-native reverse proxy and load balancer</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="s3console" target="_blank" class="tile storage">
|
||||
<span class="icon">💾</span>
|
||||
<div class="tile-eyebrow">Storage</div>
|
||||
<div class="tile-title">RustFS</div>
|
||||
<p class="tile-desc">S3-compatible object storage for all your data needs</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="git" class="tile git">
|
||||
<span class="icon">🐙</span>
|
||||
<div class="tile-eyebrow">Version Control</div>
|
||||
<div class="tile-title">Gitea</div>
|
||||
<p class="tile-desc">Self-hosted Git service with packages and CI</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="notebooks" target="_blank" class="tile notebook">
|
||||
<span class="tile-badge gpu">GPU</span>
|
||||
<span class="icon">📑</span>
|
||||
<div class="tile-eyebrow">Analysis</div>
|
||||
<div class="tile-title">Notebooks</div>
|
||||
<p class="tile-desc">Marimo reactive notebooks with CUDA acceleration</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="zotero" target="_blank" class="tile research">
|
||||
<span class="tile-badge gpu">GPU</span>
|
||||
<span class="icon">📚</span>
|
||||
<div class="tile-eyebrow">Research</div>
|
||||
<div class="tile-title">Zotero</div>
|
||||
<p class="tile-desc">Research reference manager in the cloud</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="docs" target="_blank" class="tile research">
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="icon">📖</span>
|
||||
<div class="tile-eyebrow">Documentation</div>
|
||||
<div class="tile-title">Docs</div>
|
||||
<p class="tile-desc">API reference and CMS bibliography browser</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="nessie" target="_blank" class="tile catalog">
|
||||
<span class="tile-badge data">Data</span>
|
||||
<span class="icon">🌳</span>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Nessie</div>
|
||||
<p class="tile-desc">Git-like versioned data catalog for Apache Iceberg</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="trino" target="_blank" class="tile query">
|
||||
<span class="tile-badge data">Data</span>
|
||||
<span class="icon">🔍</span>
|
||||
<div class="tile-eyebrow">Query</div>
|
||||
<div class="tile-title">Trino</div>
|
||||
<p class="tile-desc">Distributed SQL query engine for the lakehouse</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="polaris" target="_blank" class="tile catalog">
|
||||
<span class="tile-badge new">New</span>
|
||||
<span class="icon">⭐</span>
|
||||
<div class="tile-eyebrow">Catalog</div>
|
||||
<div class="tile-title">Polaris</div>
|
||||
<p class="tile-desc">Apache Iceberg catalog with governance and Unity-style access</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="grafana" target="_blank" class="tile observability">
|
||||
<span class="icon">📊</span>
|
||||
<div class="tile-eyebrow">Observability</div>
|
||||
<div class="tile-title">Grafana</div>
|
||||
<p class="tile-desc">Observability dashboards for metrics, logs, and traces</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="tempo" target="_blank" class="tile metrics">
|
||||
<span class="icon">👁</span>
|
||||
<div class="tile-eyebrow">Tracing</div>
|
||||
<div class="tile-title">Tempo</div>
|
||||
<p class="tile-desc">Distributed tracing for microservices</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="prometheus" target="_blank" class="tile logs">
|
||||
<span class="icon">📈</span>
|
||||
<div class="tile-eyebrow">Metrics</div>
|
||||
<div class="tile-title">Prometheus</div>
|
||||
<p class="tile-desc">Metrics collection and alerting</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="loki" target="_blank" class="tile database">
|
||||
<span class="icon">📋</span>
|
||||
<div class="tile-eyebrow">Logs</div>
|
||||
<div class="tile-title">Loki</div>
|
||||
<p class="tile-desc">Log aggregation system by Grafana</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
|
||||
<a data-subdomain="api" target="_blank" class="tile proxy">
|
||||
<span class="icon">⚙</span>
|
||||
<div class="tile-eyebrow">API</div>
|
||||
<div class="tile-title">Platform API</div>
|
||||
<p class="tile-desc">FastAPI platform backend</p>
|
||||
<span class="health health-unknown"><span class="health-k">health</span><span class="health-v">checking</span></span>
|
||||
<span class="tile-port"></span>
|
||||
<div class="stripe"></div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="ftr">
|
||||
<span>fhirworx.io</span> — self-hosted data lakehouse — © 2025
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function paintHealth(el, origin, sub, fqdn) {
|
||||
var box = el.querySelector('.health');
|
||||
if (!box) return;
|
||||
var value = box.querySelector('.health-v');
|
||||
function paint(kind, text) {
|
||||
box.className = 'health health-' + kind;
|
||||
value.textContent = text;
|
||||
}
|
||||
fetch(origin + '/healthz/' + sub, {cache: 'no-store'}).then(function (r) {
|
||||
if (r.status === 503) paint('warn', 'not ready');
|
||||
else if (r.ok) paint('ok', 'up');
|
||||
else paint('bad', 'down');
|
||||
}, function () {
|
||||
paint('bad', 'down');
|
||||
});
|
||||
}
|
||||
(function() {
|
||||
var root = document.querySelector('[data-health-origin]');
|
||||
var origin = root ? (root.getAttribute('data-health-origin') || '') : '';
|
||||
var parts = location.hostname.split('.');
|
||||
var base = parts.length > 2 ? parts.slice(1).join('.') : location.hostname;
|
||||
document.querySelectorAll('.homelab-dashboard [data-subdomain]').forEach(function(el) {
|
||||
var sub = el.dataset.subdomain;
|
||||
var fqdn = sub + '.' + base;
|
||||
el.href = 'https://' + fqdn + '/';
|
||||
el.target = '_blank';
|
||||
var port = el.querySelector('.tile-port');
|
||||
if (port) port.textContent = fqdn;
|
||||
paintHealth(el, origin, sub, fqdn);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,3 @@
|
||||
{{template "base/head" .}}
|
||||
{{template "homelab/landing" .}}
|
||||
{{template "base/footer" .}}
|
||||
Reference in New Issue
Block a user