Files
homelab-manifests/glance-system/glance-kisfenyo.yaml
T
2026-01-14 08:12:18 +01:00

1699 lines
73 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Glance Dashboard for Kisfenyo
# Namespace: glance-system
# Domain: kisfenyo.dooplex.hu
# Version: v0.8.4
#
# Features:
# - Custom background image
# - Custom logo
# - Weather widget (Budapest)
# - YouTube subscriptions
# - RSS feeds
# - To-do list
# - iFrames for Cal.com, Google Calendar, Outline
# - Bookmarks/Links to all apps
# - Calendar widget
#
# Authentik Integration:
# 1. Create Application: "Glance Home"
# 2. Create Provider: Proxy Provider with external host https://kisfenyo.dooplex.hu
# 3. Create Outpost: glance-outpost
# 4. Update auth-url annotation with actual outpost service name
---
apiVersion: v1
kind: ConfigMap
metadata:
name: glance-config-kisfenyo
namespace: glance-system
labels:
app.kubernetes.io/name: glance-kisfenyo
app.kubernetes.io/instance: glance-kisfenyo
data:
glance.yml: |
# Glance Configuration
# Documentation: https://github.com/glanceapp/glance/blob/main/docs/configuration.md
server:
host: 0.0.0.0
port: 8080
assets-path: /app/config/assets
branding:
logo-url: https://web.dooplex.hu/static/DooPlex_logo_3.png
favicon-url: https://web.dooplex.hu/static/DooPlex_favicon_3.png
app-name: "Kisfenyo's Home"
app-icon-url: https://web.dooplex.hu/static/DooPlex_favicon_3.png
app-background-color: "#132b66"
hide-footer: true
theme:
disable-picker: true
background-color: 210 35 12 # Was: 280 30 15 (purple → blue)
primary-color: 200 70 60 # Was: 280 60 70 (purple → cyan-blue)
positive-color: 150 55 50 # Was: 120 50 50 (green → teal-green, matches better)
negative-color: 0 70 60 # Unchanged (red)
contrast-multiplier: 1.2 # Unchanged
text-saturation-multiplier: 0.8 # Unchanged
custom-css-file: /assets/custom.css
pages:
# ==================== HOME PAGE ====================
- name: Home
slug: home
width: wide
columns:
# ---------- LEFT COLUMN ----------
- size: small
widgets:
# Glance Custom API Widget - System Stats from Prometheus
# Add this widget to your glance.yml configuration under a column's widgets section
#
# Prometheus URL: http://prometheus.mon-system.svc.cluster.local:9090
#
# This widget displays:
# - Hostname & Uptime
# - CPU usage % and Temperature
# - Memory usage %
# - Disk usage for all mount points with progress bars
# - Fan speeds
#
# Note: Make sure your Glance version is v0.8.0+ for all template functions
- type: custom-api
title: DooPlex Server
cache: 30s
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_uname_info
subrequests:
uptime_days:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: floor((time() - node_boot_time_seconds) / 86400)
uptime_hours:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: floor((time() - node_boot_time_seconds) % 86400 / 3600)
cpu:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 - (avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
memory:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100
cpu_temp:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_hwmon_temp_celsius{instance="dooplex",chip="platform_coretemp_0",sensor="temp1"}
fans:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: fan_speed_rpm{instance="dooplex"}
disk_root:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 * (1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"})
disk_root_used:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (node_filesystem_size_bytes{mountpoint="/"} - node_filesystem_avail_bytes{mountpoint="/"}) / 1073741824
disk_root_total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_filesystem_size_bytes{mountpoint="/"} / 1073741824
disk_ssd2:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 * (1 - node_filesystem_avail_bytes{mountpoint="/mnt/ssd_2"} / node_filesystem_size_bytes{mountpoint="/mnt/ssd_2"})
disk_ssd2_used:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (node_filesystem_size_bytes{mountpoint="/mnt/ssd_2"} - node_filesystem_avail_bytes{mountpoint="/mnt/ssd_2"}) / 1073741824
disk_ssd2_total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_filesystem_size_bytes{mountpoint="/mnt/ssd_2"} / 1073741824
disk_hdd1:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 * (1 - node_filesystem_avail_bytes{mountpoint="/mnt/1_hdd"} / node_filesystem_size_bytes{mountpoint="/mnt/1_hdd"})
disk_hdd1_used:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (node_filesystem_size_bytes{mountpoint="/mnt/1_hdd"} - node_filesystem_avail_bytes{mountpoint="/mnt/1_hdd"}) / 1099511627776
disk_hdd1_total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_filesystem_size_bytes{mountpoint="/mnt/1_hdd"} / 1099511627776
disk_hdd2:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 * (1 - node_filesystem_avail_bytes{mountpoint="/mnt/2_hdd"} / node_filesystem_size_bytes{mountpoint="/mnt/2_hdd"})
disk_hdd2_used:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (node_filesystem_size_bytes{mountpoint="/mnt/2_hdd"} - node_filesystem_avail_bytes{mountpoint="/mnt/2_hdd"}) / 1099511627776
disk_hdd2_total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_filesystem_size_bytes{mountpoint="/mnt/2_hdd"} / 1099511627776
disk_hdd3:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 * (1 - node_filesystem_avail_bytes{mountpoint="/mnt/3_hdd"} / node_filesystem_size_bytes{mountpoint="/mnt/3_hdd"})
disk_hdd3_used:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (node_filesystem_size_bytes{mountpoint="/mnt/3_hdd"} - node_filesystem_avail_bytes{mountpoint="/mnt/3_hdd"}) / 1099511627776
disk_hdd3_total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_filesystem_size_bytes{mountpoint="/mnt/3_hdd"} / 1099511627776
disk_hdd4:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 * (1 - node_filesystem_avail_bytes{mountpoint="/mnt/4_hdd"} / node_filesystem_size_bytes{mountpoint="/mnt/4_hdd"})
disk_hdd4_used:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (node_filesystem_size_bytes{mountpoint="/mnt/4_hdd"} - node_filesystem_avail_bytes{mountpoint="/mnt/4_hdd"}) / 1099511627776
disk_hdd4_total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_filesystem_size_bytes{mountpoint="/mnt/4_hdd"} / 1099511627776
disk_hdd5:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: 100 * (1 - node_filesystem_avail_bytes{mountpoint="/mnt/5_hdd"} / node_filesystem_size_bytes{mountpoint="/mnt/5_hdd"})
disk_hdd5_used:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: (node_filesystem_size_bytes{mountpoint="/mnt/5_hdd"} - node_filesystem_avail_bytes{mountpoint="/mnt/5_hdd"}) / 1099511627776
disk_hdd5_total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: node_filesystem_size_bytes{mountpoint="/mnt/5_hdd"} / 1099511627776
template: |
<style>
.sys-stats { font-size: 0.9em; }
.top-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px; }
.top-item { text-align: center; padding: 6px 8px; background: rgba(255,255,255,0.05); border-radius: 8px; }
.top-item.wide { grid-column: span 2; }
.top-label { font-size: 0.65em; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.5px; }
.top-value { font-size: 1.05em; font-weight: 600; margin-top: 2px; }
.section-title { font-size: 0.75em; opacity: 0.6; text-transform: uppercase; letter-spacing: 0.5px; margin: 10px 0 6px 0; }
.disk-row { display: flex; align-items: center; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08); }
.disk-row:last-child { border-bottom: none; }
.disk-name { font-weight: 500; width: 42px; font-size: 0.95em; flex-shrink: 0; }
.disk-bar { flex: 1; height: 8px; background: rgba(255,255,255,0.1); border-radius: 4px; margin: 0 10px; overflow: hidden; min-width: 0; }
.disk-fill { height: 100%; border-radius: 4px; }
.fill-ok { background: linear-gradient(90deg, #4ade80, #22c55e); }
.fill-warn { background: linear-gradient(90deg, #fbbf24, #f59e0b); }
.fill-crit { background: linear-gradient(90deg, #f87171, #ef4444); }
.disk-info { font-size: 0.9em; opacity: 0.85; text-align: right; width: 125px; flex-shrink: 0; }
</style>
{{ $hostname := .JSON.String "data.result.0.metric.nodename" }}
{{ $uptimeDays := (.Subrequest "uptime_days").JSON.Float "data.result.0.value.1" }}
{{ $uptimeHours := (.Subrequest "uptime_hours").JSON.Float "data.result.0.value.1" }}
{{ $cpu := (.Subrequest "cpu").JSON.Float "data.result.0.value.1" }}
{{ $mem := (.Subrequest "memory").JSON.Float "data.result.0.value.1" }}
{{ $temp := (.Subrequest "cpu_temp").JSON.Float "data.result.0.value.1" }}
{{ $fans := (.Subrequest "fans").JSON.Array "data.result" }}
<div class="sys-stats">
<div class="top-grid">
<div class="top-item">
<div class="top-label">Host</div>
<div class="top-value">{{ $hostname }}</div>
</div>
<div class="top-item">
<div class="top-label">CPU</div>
<div class="top-value">{{ printf "%.1f" $cpu }}%</div>
</div>
<div class="top-item">
<div class="top-label">Uptime</div>
<div class="top-value">{{ printf "%.0f" $uptimeDays }}d {{ printf "%.0f" $uptimeHours }}h</div>
</div>
<div class="top-item">
<div class="top-label">Memory</div>
<div class="top-value">{{ printf "%.1f" $mem }}%</div>
</div>
<div class="top-item">
<div class="top-label">Temp</div>
<div class="top-value">{{ printf "%.0f" $temp }}°C</div>
</div>
<div class="top-item">
<div class="top-label">Fans (RPM)</div>
<div class="top-value">{{ range $i, $fan := $fans }}{{ if $i }}/{{ end }}{{ $fan.Int "value.1" }}{{ end }}</div>
</div>
</div>
<div class="section-title">Storage</div>
{{ $rootPct := (.Subrequest "disk_root").JSON.Float "data.result.0.value.1" }}
{{ $rootUsed := (.Subrequest "disk_root_used").JSON.Float "data.result.0.value.1" }}
{{ $rootTotal := (.Subrequest "disk_root_total").JSON.Float "data.result.0.value.1" }}
<div class="disk-row">
<span class="disk-name">Root</span>
<div class="disk-bar">
<div class="disk-fill {{ if lt $rootPct 70.0 }}fill-ok{{ else if lt $rootPct 85.0 }}fill-warn{{ else }}fill-crit{{ end }}" style="width: {{ printf "%.0f" $rootPct }}%"></div>
</div>
<span class="disk-info">{{ printf "%.0f" $rootUsed }} / {{ printf "%.0f" $rootTotal }} GB ({{ printf "%.0f" $rootPct }}%)</span>
</div>
{{ $ssd2Pct := (.Subrequest "disk_ssd2").JSON.Float "data.result.0.value.1" }}
{{ $ssd2Used := (.Subrequest "disk_ssd2_used").JSON.Float "data.result.0.value.1" }}
{{ $ssd2Total := (.Subrequest "disk_ssd2_total").JSON.Float "data.result.0.value.1" }}
<div class="disk-row">
<span class="disk-name">SSD2</span>
<div class="disk-bar">
<div class="disk-fill {{ if lt $ssd2Pct 70.0 }}fill-ok{{ else if lt $ssd2Pct 85.0 }}fill-warn{{ else }}fill-crit{{ end }}" style="width: {{ printf "%.0f" $ssd2Pct }}%"></div>
</div>
<span class="disk-info">{{ printf "%.0f" $ssd2Used }} / {{ printf "%.0f" $ssd2Total }} GB ({{ printf "%.0f" $ssd2Pct }}%)</span>
</div>
{{ $hdd1Pct := (.Subrequest "disk_hdd1").JSON.Float "data.result.0.value.1" }}
{{ $hdd1Used := (.Subrequest "disk_hdd1_used").JSON.Float "data.result.0.value.1" }}
{{ $hdd1Total := (.Subrequest "disk_hdd1_total").JSON.Float "data.result.0.value.1" }}
<div class="disk-row">
<span class="disk-name">HDD1</span>
<div class="disk-bar">
<div class="disk-fill {{ if lt $hdd1Pct 70.0 }}fill-ok{{ else if lt $hdd1Pct 85.0 }}fill-warn{{ else }}fill-crit{{ end }}" style="width: {{ printf "%.0f" $hdd1Pct }}%"></div>
</div>
<span class="disk-info">{{ printf "%.1f" $hdd1Used }} / {{ printf "%.1f" $hdd1Total }} TB ({{ printf "%.0f" $hdd1Pct }}%)</span>
</div>
{{ $hdd2Pct := (.Subrequest "disk_hdd2").JSON.Float "data.result.0.value.1" }}
{{ $hdd2Used := (.Subrequest "disk_hdd2_used").JSON.Float "data.result.0.value.1" }}
{{ $hdd2Total := (.Subrequest "disk_hdd2_total").JSON.Float "data.result.0.value.1" }}
<div class="disk-row">
<span class="disk-name">HDD2</span>
<div class="disk-bar">
<div class="disk-fill {{ if lt $hdd2Pct 70.0 }}fill-ok{{ else if lt $hdd2Pct 85.0 }}fill-warn{{ else }}fill-crit{{ end }}" style="width: {{ printf "%.0f" $hdd2Pct }}%"></div>
</div>
<span class="disk-info">{{ printf "%.1f" $hdd2Used }} / {{ printf "%.1f" $hdd2Total }} TB ({{ printf "%.0f" $hdd2Pct }}%)</span>
</div>
{{ $hdd3Pct := (.Subrequest "disk_hdd3").JSON.Float "data.result.0.value.1" }}
{{ $hdd3Used := (.Subrequest "disk_hdd3_used").JSON.Float "data.result.0.value.1" }}
{{ $hdd3Total := (.Subrequest "disk_hdd3_total").JSON.Float "data.result.0.value.1" }}
<div class="disk-row">
<span class="disk-name">HDD3</span>
<div class="disk-bar">
<div class="disk-fill {{ if lt $hdd3Pct 70.0 }}fill-ok{{ else if lt $hdd3Pct 85.0 }}fill-warn{{ else }}fill-crit{{ end }}" style="width: {{ printf "%.0f" $hdd3Pct }}%"></div>
</div>
<span class="disk-info">{{ printf "%.1f" $hdd3Used }} / {{ printf "%.1f" $hdd3Total }} TB ({{ printf "%.0f" $hdd3Pct }}%)</span>
</div>
{{ $hdd4Pct := (.Subrequest "disk_hdd4").JSON.Float "data.result.0.value.1" }}
{{ $hdd4Used := (.Subrequest "disk_hdd4_used").JSON.Float "data.result.0.value.1" }}
{{ $hdd4Total := (.Subrequest "disk_hdd4_total").JSON.Float "data.result.0.value.1" }}
<div class="disk-row">
<span class="disk-name">HDD4</span>
<div class="disk-bar">
<div class="disk-fill {{ if lt $hdd4Pct 70.0 }}fill-ok{{ else if lt $hdd4Pct 85.0 }}fill-warn{{ else }}fill-crit{{ end }}" style="width: {{ printf "%.0f" $hdd4Pct }}%"></div>
</div>
<span class="disk-info">{{ printf "%.1f" $hdd4Used }} / {{ printf "%.1f" $hdd4Total }} TB ({{ printf "%.0f" $hdd4Pct }}%)</span>
</div>
{{ $hdd5Pct := (.Subrequest "disk_hdd5").JSON.Float "data.result.0.value.1" }}
{{ $hdd5Used := (.Subrequest "disk_hdd5_used").JSON.Float "data.result.0.value.1" }}
{{ $hdd5Total := (.Subrequest "disk_hdd5_total").JSON.Float "data.result.0.value.1" }}
<div class="disk-row">
<span class="disk-name">HDD5</span>
<div class="disk-bar">
<div class="disk-fill {{ if lt $hdd5Pct 70.0 }}fill-ok{{ else if lt $hdd5Pct 85.0 }}fill-warn{{ else }}fill-crit{{ end }}" style="width: {{ printf "%.0f" $hdd5Pct }}%"></div>
</div>
<span class="disk-info">{{ printf "%.1f" $hdd5Used }} / {{ printf "%.1f" $hdd5Total }} TB ({{ printf "%.0f" $hdd5Pct }}%)</span>
</div>
</div>
- type: bookmarks
title: Admin Tools
groups:
- links:
- title: Code-Server
url: https://code.dooplex.hu
icon: si:coder
- title: ArgoCD
url: https://argocd.dooplex.hu
icon: si:argo
- title: Gitea
url: https://gitea.dooplex.hu
icon: si:gitea
- title: Headlamp
url: https://headlamp.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/headlamp.png
- title: Authentik
url: https://authentik.dooplex.hu
icon: si:authentik
- title: Termix
url: https://termix.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/termix.png
- title: Longhorn (LAN Only)
url: http://192.168.0.209/#/dashboard
icon: https://web.dooplex.hu/static/white-icons/longhorn.png
- title: Pi-hole
url: https://pihole.dooplex.hu/admin
icon: si:pihole
# ---------- CENTER COLUMN ----------
- size: full
widgets:
- type: split-column
max-columns: 3
widgets:
# Weather Widget
- type: weather
location: Budapest, Hungary
units: metric
hour-format: 24h
# Calendar Widget
- type: calendar
first-day-of-week: monday
# To-Do List
- type: to-do
title: Tasks
# Outline Notes iframe
- type: iframe
source: https://outline.dooplex.hu/collection/dooplex-server-iTAZn04AaR/recent
height: 500
title: Documentation
# ---------- RIGHT COLUMN ----------
- size: small
widgets:
- type: bookmarks
title: Useful Bookmarks
groups:
- title: ""
links:
- title: GMail
url: https://mail.google.com
icon: si:gmail
- title: Kréta
url: https://klik100566001.e-kreta.hu/Intezmeny/Faliujsag
icon: https://web.dooplex.hu/static/white-icons/kreta.png
- title: Hardverapró
url: https://hardverapro.hu
icon: https://web.dooplex.hu/static/white-icons/hardverapro.png
- type: bookmarks
title: Productivity Self-Hosted
groups:
- title: ""
links:
- title: Nextcloud
url: https://nextcloud.dooplex.hu
icon: si:nextcloud
- title: Outline
url: https://outline.dooplex.hu
icon: si:outline
- title: Paperless
url: https://paperless.dooplex.hu
icon: si:paperlessngx
- title: Vaultwarden
url: https://vaultwarden.dooplex.hu
icon: si:bitwarden
- title: Actual Budget
url: https://actualbudget.dooplex.hu
icon: si:actualbudget
- title: Tandoor
url: https://tandoor.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/tandoor.png
- title: Bookstack
url: https://bookstack.dooplex.hu
icon: si:bookstack
- type: bookmarks
title: Other Self-Hosted
groups:
- links:
- title: AdventureLog
url: https://adventures.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/adventurelog.png
- title: Wanderer
url: https://wanderer.dooplex.hu
icon: sh:wanderer
- title: Plant-it
url: https://plantit.dooplex.hu
icon: si:leaflet
- title: Workout (wger)
url: https://workout.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/wger.png
- title: Fileshare
url: https://fileshare.dooplex.hu
icon: si:files
- title: Privatebin
url: https://privatebin.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/privatebin.png
- title: Pastes (OpenGist)
url: https://paste.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/opengist.png
- title: Zipline
url: https://zipline.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/zipline.png
# ==================== MONITORING PAGE ====================
- name: Monitoring
slug: monitoring
width: wide
columns:
- size: small
widgets:
- type: bookmarks
title: Monitoring
groups:
- links:
- title: Grafana
url: https://grafana.dooplex.hu/d/adxgb7x/overview-dashboard?orgId=1&from=now-3h&to=now&timezone=browser
icon: si:grafana
- title: Uptime Kuma
url: https://uptimekuma.dooplex.hu
icon: si:uptimekuma
- title: Prometheus (LAN Only)
url: http://prometheus.home/alerts
icon: si:prometheus
# Glance Widget: Container Version Checker (Simplified)
#
# This is a more robust version that uses only Glance's documented template functions.
# Add this widget to your glance.yml under a column's widgets section.
# Place it right after the "DooPlex Server" widget.
#
# Prerequisites:
# - Deploy version-checker from version-checker.yaml
# - Wait ~5 minutes for initial version checks to complete
- type: custom-api
title: Container Versions
cache: 5m
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: >
max by (image, current_version, latest_version) (
version_checker_is_latest_version{
container_type="container",
image!~"(^|.*/)(busybox|redis|alpine|nginx|mariadb|mysql|valkey)$",
image!~"^longhornio.*$",
image!~"(^|.*/)postgres.*$",
image!~"^registry\\.k8s\\.io/ingress-nginx/.*$",
current_version!~".*sha256:.*",
latest_version!~".*sha256:.*"
} == 0
)
subrequests:
up_to_date:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: >
count(
max by (image) (
version_checker_is_latest_version{
container_type="container",
image!~"(^|.*/)(busybox|redis|alpine|nginx|mariadb|mysql|valkey)$",
image!~"^longhornio.*$",
image!~"(^|.*/)postgres.*$",
image!~"^registry\\.k8s\\.io/ingress-nginx/.*$",
current_version!~".*sha256:.*",
latest_version!~".*sha256:.*"
} == 1
)
) or vector(0)
outdated:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: >
count(
max by (image) (
version_checker_is_latest_version{
container_type="container",
image!~"(^|.*/)(busybox|redis|alpine|nginx|mariadb|mysql|valkey)$",
image!~"^longhornio.*$",
image!~"(^|.*/)postgres.*$",
image!~"^registry\\.k8s\\.io/ingress-nginx/.*$",
current_version!~".*sha256:.*",
latest_version!~".*sha256:.*"
} == 0
)
) or vector(0)
total:
url: ${PROMETHEUS_URL}/api/v1/query
parameters:
query: >
count(
max by (image) (
version_checker_is_latest_version{
container_type="container",
image!~"(^|.*/)(busybox|redis|alpine|nginx|mariadb|mysql|valkey)$",
image!~"^longhornio.*$",
image!~"(^|.*/)postgres.*$",
image!~"^registry\\.k8s\\.io/ingress-nginx/.*$",
current_version!~".*sha256:.*",
latest_version!~".*sha256:.*"
}
)
) or vector(0)
template: |
<style>
.ver-widget { font-size: 0.9em; }
.ver-summary {
display: flex;
gap: 12px;
margin-bottom: 12px;
padding: 8px;
background: rgba(255,255,255,0.03);
border-radius: 8px;
}
.ver-stat {
flex: 1;
text-align: center;
padding: 4px;
}
.ver-label {
font-size: 0.7em;
opacity: 0.6;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.ver-value {
font-size: 1.4em;
font-weight: 600;
margin-top: 2px;
}
.ver-ok { color: #4ade80; }
.ver-warn { color: #fbbf24; }
.ver-info { color: #5ac8d8; }
.ver-list {
max-height: 220px;
overflow-y: auto;
}
.ver-row {
display: grid;
grid-template-columns: 1fr auto auto auto;
align-items: center;
gap: 4px;
padding: 5px 0;
border-bottom: 1px solid rgba(255,255,255,0.08);
font-size: 0.82em;
}
.ver-row:last-child { border-bottom: none; }
.ver-img {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
opacity: 0.9;
}
.ver-cur {
color: #f87171;
font-family: monospace;
font-size: 0.95em;
}
.ver-arr {
color: rgba(255,255,255,0.3);
padding: 0 2px;
}
.ver-lat {
color: #4ade80;
font-family: monospace;
font-size: 0.95em;
}
.ver-allok {
text-align: center;
padding: 16px;
color: #4ade80;
opacity: 0.85;
}
.ver-nodata {
text-align: center;
padding: 16px;
opacity: 0.5;
font-size: 0.9em;
}
</style>
{{ $upToDate := (.Subrequest "up_to_date").JSON.Float "data.result.0.value.1" }}
{{ $outdated := (.Subrequest "outdated").JSON.Float "data.result.0.value.1" }}
{{ $total := (.Subrequest "total").JSON.Float "data.result.0.value.1" }}
{{ $updates := .JSON.Array "data.result" }}
<div class="ver-widget">
{{ if gt $total 0.0 }}
<div class="ver-summary">
<div class="ver-stat">
<div class="ver-label">Current</div>
<div class="ver-value ver-ok">{{ printf "%.0f" $upToDate }}</div>
</div>
<div class="ver-stat">
<div class="ver-label">Updates</div>
<div class="ver-value ver-warn">{{ printf "%.0f" $outdated }}</div>
</div>
<div class="ver-stat">
<div class="ver-label">Total</div>
<div class="ver-value ver-info">{{ printf "%.0f" $total }}</div>
</div>
</div>
{{ if gt $outdated 0.0 }}
<div class="ver-list">
{{ range $updates }}
<div class="ver-row" title="{{ .String "metric.image" }}">
<span class="ver-img">{{ trimPrefix "ghcr.io/" (trimPrefix "docker.io/" (trimPrefix "lscr.io/" (trimPrefix "quay.io/" (.String "metric.image")))) }}</span>
<span class="ver-cur">{{ .String "metric.current_version" }}</span>
<span class="ver-arr">→</span>
<span class="ver-lat">{{ .String "metric.latest_version" }}</span>
</div>
{{ end }}
</div>
{{ else }}
<div class="ver-allok">✓ All images up to date!</div>
{{ end }}
{{ else }}
<div class="ver-nodata">Waiting for version-checker metrics...<br><small>Check back in a few minutes</small></div>
{{ end }}
</div>
- size: full
widgets:
# Grafana
- type: iframe
source: https://grafana.dooplex.hu/d/nazwb7z/overview-dashboard-for-glance?kiosk
height: 1140
title: Grafana Overview Dashboard
- size: small
widgets:
- type: custom-api
title: Uptime Kuma
title-url: ${UPTIME_KUMA_PUBLIC_URL}
url: ${UPTIME_KUMA_URL}/api/status-page/${UPTIME_KUMA_STATUS_SLUG}
subrequests:
heartbeats:
url: ${UPTIME_KUMA_URL}/api/status-page/heartbeat/${UPTIME_KUMA_STATUS_SLUG}
cache: 10m
template: |
{{ $hb := .Subrequest "heartbeats" }}
{{ if not (.JSON.Exists "publicGroupList") }}
<p class="color-negative">Error reading response</p>
{{ else if eq (len (.JSON.Array "publicGroupList")) 0 }}
<p>No monitors found</p>
{{ else }}
<ul class="dynamic-columns list-gap-8">
{{ range .JSON.Array "publicGroupList" }}
{{ range .Array "monitorList" }}
{{ $id := .String "id" }}
{{ $hbArray := $hb.JSON.Array (print "heartbeatList." $id) }}
<div class="flex items-center gap-12">
<a class="size-title-dynamic color-highlight text-truncate block grow" href="${UPTIME_KUMA_URL}/dashboard/{{ $id }}"
target="_blank" rel="noreferrer">
{{ .String "name" }} </a>
{{ if gt (len $hbArray) 0 }}
{{ $latest := index $hbArray (sub (len $hbArray) 1) }}
{{ if eq ($latest.Int "status") 1 }}
<div>{{ $latest.Int "ping" }}ms</div>
<div class="monitor-site-status-icon-compact" title="OK">
<svg fill="var(--color-positive)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z"
clip-rule="evenodd"></path>
</svg>
</div>
{{ else }}
<div><span class="color-negative">DOWN</span></div>
<div class="monitor-site-status-icon-compact" title="{{ if $latest.Exists "msg" }}{{ $latest.String "msg" }}{{ else
}}Error{{ end }}">
<svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path fill-rule="evenodd"
d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495ZM10 5a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 10 5Zm0 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
clip-rule="evenodd"></path>
</svg>
</div>
{{ end }}
{{ else }}
<div><span class="color-negative">No data</span></div>
<div class="monitor-site-status-icon-compact" title="No data available">
<svg fill="var(--color-negative)" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M10 18a8 8 0 1 1 0-16 8 8 0 0 1 0 16zm0-2a6 6 0 1 0 0-12 6 6 0 0 0 0 12zm-.75-8a.75.75 0 0 1 1.5 0v3a.75.75 0 0 1-1.5 0V8zm.75 5a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
</svg>
</div>
{{ end }}
</div>
{{ end }}
{{ end }}
</ul>
{{ end }}
# ==================== MEDIA PAGE ====================
- name: Media
slug: media
width: wide
columns:
- size: small
widgets:
- type: bookmarks
title: Entertainment
groups:
- links:
- title: Plex
url: https://plex.dooplex.hu
icon: si:plex
- title: Immich (Photos)
url: https://photos.dooplex.hu
icon: si:immich
- title: AudioBookshelf
url: https://audiobookshelf.dooplex.hu
icon: si:audiobookshelf
- title: Calibre-Web (eBooks)
url: https://books.dooplex.hu
icon: si:calibreweb
- title: Arcade (Retro Games)
url: https://arcade.dooplex.hu
icon: si:retroarch
- title: Crafty Controller
url: https://crafty.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/minecraftcreeper.png
- title: YouTube
url: https://www.youtube.com
icon: si:youtube
- title: Spotify
url: https://open.spotify.com/
icon: si:spotify
- type: bookmarks
title: Media Management
groups:
- links:
- title: Sonarr (TV Shows)
url: https://sonarr.dooplex.hu
icon: si:sonarr
- title: Radarr (Movies)
url: https://radarr.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/radarr.png
- title: RadarrKids
url: https://radarrkids.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/radarrkids.png
- title: Prowlarr (Indexers)
url: https://prowlarr.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/prowlarr.png
- title: Seerr (Requests)
url: https://seerr.dooplex.hu
icon: https://web.dooplex.hu/static/white-icons/seerr.png
- type: custom-api
title: Crafty - Minecraft Server
cache: 5s
options:
base-url: ${CRAFTY_URL}
api-key: ${CRAFTY_API_TOKEN}
server-id: ${CRAFTY_SERVER_ID}
display-MOTD: true
allow-insecure: true
template: |
{{/* Required config options */}}
{{ $baseURL := .Options.StringOr "base-url" "" }}
{{ $apiKey := .Options.StringOr "api-key" "" }}
{{ $serverID := .Options.StringOr "server-id" "" }}
{{/* Optional config options */}}
{{ $displayMOTD := .Options.BoolOr "display-MOTD" true }}
{{ $serverStats := newRequest (print $baseURL "/api/v2/servers/" $serverID "/stats")
| withHeader "Authorization" (print "Bearer " $apiKey)
| withHeader "Accept" "application/json"
| getResponse }}
{{ $is_running := $serverStats.JSON.Bool "data.running" }}
{{ $online_players := $serverStats.JSON.Int "data.online" | formatNumber }}
{{ $max_players := $serverStats.JSON.Int "data.max" | formatNumber }}
{{ $name := $serverStats.JSON.String "data.world_name" }}
{{ $size := $serverStats.JSON.String "data.world_size" }}
{{ $version := $serverStats.JSON.String "data.version" }}
{{ $icon := $serverStats.JSON.String "data.icon" }}
{{ $server_ip := $serverStats.JSON.String "data.server_id.server_ip" }}
{{ $server_port := $serverStats.JSON.String "data.server_id.server_port" }}
{{ $motd := $serverStats.JSON.String "data.desc" }}
{{ $server_addr := "" }}
{{ if and ($is_running) (eq $server_ip "127.0.0.1") }}
{{ $server_addr = printf "%s:%s" (replaceMatches "https?://" "" $baseURL) $server_port }}
{{ else if $is_running }}
{{ $server_addr = printf "%s:%s" $server_ip $server_port }}
{{ end }}
{{ $starting := false }}
{{ if and ($is_running) (eq $max_players "0") (eq $version "False") }}
{{ $starting = true }}
{{ end }}
<!-- I couldn't find documentation describing the "waiting_start" state or the other booleans below. Implementation might not be correct. -->
{{ $updating := $serverStats.JSON.Bool "data.updating" }}
{{ $importing := $serverStats.JSON.Bool "data.importing" }}
{{ $crashed := $serverStats.JSON.Bool "data.crashed" }}
<div style="display:flex; align-items:center; gap:12px;">
<!-- Server Icon -->
<div style="width:40px; height:40px; flex-shrink:0; border-radius:4px; display:flex; justify-content:center; align-items:center; overflow:hidden;">
{{ if eq $icon "" }}
<img src="https://cdn.jsdelivr.net/gh/selfhst/icons/png/minecraft.png" style="width:100%; height:100%; object-fit:contain;" alt="Server icon">
{{ else }}
<img src="data:image/png;base64, {{ $icon }}" style="width:100%; height:100%; object-fit:contain;" alt="Server icon">
{{ end }}
</div>
<!-- Right side: Info -->
<div style="display:flex; flex-direction:column;">
<!-- First row: Server Name + IP -->
<div style="display:flex; align-items:center; gap:6px;">
<span class="size-h4 block text-truncate color-primary">
{{ $name }}
</span>
{{ if and ($is_running) (not $starting) (not (eq $server_addr "")) }}
<div style="font-size:0.9em; color:var(--color-secondary);">
<span class="size-h6 color-secondary">
- {{ $server_addr }}
</span>
</div>
{{ end }}
</div>
<!-- Second row: MOTD & Stats if server is running, otherwise show status msg ... -->
{{ if and ($is_running) (not $starting) }}
{{ if and (not (eq $motd "")) ($displayMOTD) }}
<div style="font-size:0.9em; color:var(--color-secondary);">
{{ replaceMatches "§." "" $motd }}
</div>
{{ end }}
<div style="font-size:0.9em; color:var(--color-secondary);">
{{ $version }} - {{ $online_players }}/{{ $max_players }} players - {{ $size }}
</div>
<!-- lots of assumptions about the boolean states meanings from crafty api.. -->
{{ else if $starting }}
<div style="font-size:0.9em; color:var(--color-secondary);">Server is starting up..</div>
{{ else if $importing }}
<div style="font-size:0.9em; color:var(--color-secondary);">Server is being imported..</div>
{{ else if $updating }}
<div style="font-size:0.9em; color:var(--color-secondary);">Server is being updated..</div>
{{ else if $crashed }}
<div style="font-size:0.9em; color:var(--color-secondary);">Server has crashed!</div>
{{ else }}
<div style="font-size:0.9em; color:var(--color-secondary);">Server is offline</div>
{{ end }}
</div>
</div>
- type: custom-api
title: RomM
cache: 1d
url: http://${ROMM_URL}/api/stats
headers:
Accept: application/json
template: |
{{ $bytes := .JSON.Int "TOTAL_FILESIZE_BYTES" | toFloat }}
{{ $tb := div $bytes 1099511627776 }}
{{ $gb := div $bytes 1073741824 | toInt }}
<div class="flex justify-between text-center">
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "PLATFORMS" | formatNumber }}</div>
<div class="size-h6">PLATFORMS</div>
</div>
<div>
<div class="color-highlight size-h3">{{ .JSON.Int "ROMS" | formatNumber }}</div>
<div class="size-h6">ROMS</div>
</div>
<div>
<div class="color-highlight size-h3">
{{ if ge $tb 1.0 }}
{{ printf "%.2f" $tb }}TB
{{ else }}
{{ $gb }}GB
{{ end }}
</div>
<div class="size-h6">FILESIZE</div>
</div>
</div>
- type: custom-api
title: Steam Specials
cache: 12h
url: https://store.steampowered.com/api/featuredcategories?cc=us
template: |
<ul class="list list-gap-10 collapsible-container" data-collapse-after="5">
{{ range .JSON.Array "specials.items" }}
{{ $header := .String "header_image" }}
{{ $urlPrefix := "https://store.steampowered.com/sub/" }}
{{ if findMatch "/steam/apps/" $header }}
{{ $urlPrefix = "https://store.steampowered.com/app/" }}
{{ end }}
<li>
<a class="size-h4 color-highlight block text-truncate" href="{{ $urlPrefix }}{{ .Int "id" }}/">{{ .String "name" }}</a>
<ul class="list-horizontal-text">
<li>{{ .Int "final_price" | toFloat | mul 0.01 | printf "$%.2f" }}</li>
{{ $discount := .Int "discount_percent" }}
<li{{ if ge $discount 40 }} class="color-positive"{{ end }}>{{ $discount }}% off</li>
</ul>
</li>
{{ end }}
</ul>
- size: full
widgets:
# YouTube Videos
- type: videos
title: YouTube - Gaming
channels:
- UC4Xj6emHTXnKHUq8btUuN6A #Retromation
- UC5ib5bTflXtyIkoF_l7OCHw #Olexa
- UCfWybrB-Faa30sXUE6eqMIQ #Zarfen the Loot Goblin
- UCto7D1L-MiRoOziCXK9uT5Q #Let's Game It Out
limit: 12
collapse-after: 6
- type: videos
title: YouTube - Tech
channels:
- UCXuqSBlHAE6Xw-yeJA0Tunw #LinusTechTips
- UCdngmbVKX1Tgre699-XLlUA #Techworld with Nana
- UCJa14zeVf8p6clixTOIOVyQ #Jakkuh (Jake)
- UC-2YHgc363EdcusLIBbgxzg #JoeScott
- UCY1kMZp36IQSyNx_9h4mpCg #Mark Rober
- UC7IcJI8PUf5Z3zKxnZvTBog #The School of Life
limit: 12
collapse-after: 6
- type: videos
title: YouTube - Other
channels:
- UC9qpYwK7N9EB0-SECANa23g #Jólvanezígy
- UCEFpEvuosfPGlV1VyUF6QOA #Partizán
- UC_fAjvoGnqySaNM6DRfFiCA #Pottyondi
- UCM-1sd-cXSuCsfWp8QMY_OQ #Telex.hu
- UCdPhEv5wiw2uK6J0_wkc-RA #Bűnvadászok
- UCQAeX1_gw45xb3Cg-OwEFcw #Friderikusz
- UCW5OrUZ4SeUYkUg1XqcjFYA #GeoWizard
limit: 12
collapse-after: 6
# Reddit
- type: group
title: Reddit
widgets:
- type: reddit
subreddit: hungary
show-thumbnails: true
- type: reddit
subreddit: selfhosted
show-thumbnails: true
- type: reddit
subreddit: homeserver
show-thumbnails: true
- type: reddit
subreddit: homelab
show-thumbnails: true
- type: reddit
subreddit: kubernetes
show-thumbnails: true
- type: reddit
subreddit: linux
show-thumbnails: true
- type: reddit
subreddit: sysadmin
show-thumbnails: true
- type: reddit
subreddit: technology
show-thumbnails: true
- type: reddit
subreddit: futurology
show-thumbnails: true
- size: small
widgets:
# RSS Feeds - Add your favorite feeds here
- type: rss
title: News & Feeds
limit: 15
collapse-after: 15
feeds:
- url: https://telex.hu/rss
title: telex.hu
limit: 3
- url: https://444.hu/feed
title: 444.hu
limit: 3
- url: https://444.hu/feed
title: 444.hu
limit: 3
- url: https://hvg.hu/rss
title: hvg.hu
limit: 3
# ==================== NEXTCLOUD PAGE ====================
- name: NextCloud
slug: nextcloud
width: wide
columns:
- size: full
widgets:
# Nextcloud iframe
- type: iframe
css-class: iframe-no-tint
source: https://nextcloud.dooplex.hu/apps/files/files
height: 1200
title: NextCloud
custom.css: |
/* =========================================================================
WALLPAPER VISIBLE
========================================================================= */
html, body { height: 100%; }
html {
background: url("https://web.dooplex.hu/static/wallpaper-2.jpg") center / cover no-repeat fixed !important;
}
/* Glance containers that tend to paint over the wallpaper */
body,
.page,
#page-content,
.page-content,
.content-bounds,
.page-columns,
.page-column {
background: transparent !important;
}
/* Optional readability veil (Homepage-like) */
body::before {
content: "";
position: fixed;
inset: 0;
background: rgba(20, 10, 30, 0.25);
pointer-events: none;
z-index: 0;
}
body > * { position: relative; z-index: 1; }
/* =========================================================================
ROOT VARIABLES OVERRIDE
These override Glance's default theme colors at the CSS variable level
========================================================================= */
:root {
/* Primary color - affects many built-in elements */
--color-primary: hsl(190, 70%, 60%) !important;
/* These control various UI elements */
--color-text-highlight: #5ac8d8 !important;
--color-text-accent: #5ac8d8 !important;
}
/* =========================================================================
GLOBAL LINK COLORS
Affects all <a> tags site-wide (bookmarks, reddit links, video titles, etc.)
========================================================================= */
a {
color: #5ac8d8 !important; /* CYAN - main link color */
}
a:hover {
color: #7ed9e6 !important; /* LIGHTER CYAN - hover state */
}
/* Visited links - slightly muted */
a:visited {
color: #4ab8c8 !important; /* SLIGHTLY DARKER CYAN */
}
/* =========================================================================
HEADER / NAVIGATION
The top bar with Home, Media, NextCloud tabs
========================================================================= */
/* Push nav items to bottom of header and align properly */
.header.flex {
align-items: flex-end !important;
}
.header.flex > .nav.flex {
height: 100% !important;
align-items: flex-end !important;
padding-bottom: 0 !important; /* Remove extra padding */
}
/* Nav item text styling */
.header .nav .nav-item,
.header.flex > .nav.flex > .nav-item {
color: #5ac8d8 !important; /* CYAN - nav text color */
font-size: 20px !important;
line-height: 1 !important;
padding: 8px 14px 12px 14px !important; /* top right bottom left */
letter-spacing: 0.3px !important;
text-transform: uppercase !important;
font-weight: 500 !important;
display: flex !important;
align-items: flex-end !important;
height: auto !important; /* Let it size naturally */
}
/* Nav item hover */
.header .nav .nav-item:hover {
color: #7ed9e6 !important; /* LIGHTER CYAN on hover */
}
/* Active tab underline - position closer to text */
.header .nav .nav-item-current::after,
.header .nav .nav-item[aria-current="page"]::after {
bottom: 4px !important; /* Closer to text */
background-color: #5ac8d8 !important; /* CYAN underline */
}
.header {
min-height: 80px !important;
align-items: flex-end !important;
}
/* This matches your DOM: <div class="logo"> <img ...> */
.logo img {
max-height: 100px !important;
height: auto !important;
width: auto !important;
object-fit: contain !important;
}
/* =========================================================================
HEADER BAR - Transparent background
========================================================================= */
.header-container,
.header-container.content-bounds,
.header,
.header.flex,
div.header-container {
background: transparent !important;
background-color: transparent !important;
}
/* =========================================================================
WIDGET TITLES
The "WEATHER", "NEWS & FEEDS", etc. headers on each widget
========================================================================= */
/* The header container */
.widget-header {
display: flex !important;
align-items: center !important;
justify-content: flex-start !important;
min-height: 2.2em !important;
padding: 0.5em 0 0.5em 12px !important; /* top right bottom LEFT */
box-sizing: border-box !important;
}
/* The h2 title text inside */
.widget-header h2,
.widget-header .uppercase,
h2.uppercase {
color: #5ac8d8 !important;
font-weight: 600 !important;
margin: 0 !important;
padding: 0 !important;
line-height: 1 !important;
}
/* =========================================================================
REMOVE DEFAULT BORDER & ADD CYAN LINE between header and content
========================================================================= */
/* Nuclear option - remove ALL borders from widget-content */
.widget-content,
.widget-content:not(.widget-content-frameless),
.widget-content-frame,
.widget-content:first-of-type,
.widget > .widget-content,
[class*="widget-content"] {
border: 0 !important;
border-top: 0 !important;
border-width: 0 !important;
border-style: none !important;
border-color: transparent !important;
box-shadow: none !important;
outline: none !important;
--color-widget-content-border: transparent !important;
background-color: rgba(20, 50, 70, 0.35) !important; /* DARK BLUE, semi-transparent */
}
/* Add cyan line via pseudo-element on header bottom */
.widget-header::after {
content: "" !important;
display: block !important;
position: absolute !important;
bottom: 0 !important;
left: 12px !important;
right: 12px !important;
height: 1px !important;
background: rgba(90, 200, 216, 0.4) !important; /* CYAN line */
}
/* Ensure header is positioned for the pseudo-element */
.widget-header {
position: relative !important;
}
/* =========================================================================
WIDGET BACKGROUNDS
Semi-transparent backgrounds for widget containers
========================================================================= */
/* Standard widgets (calendar, weather, to-do, RSS, etc.) */
.widget,
.widget-type-calendar,
.widget-type-weather,
.widget-type-to-do,
.widget-type-rss,
.widget-type-videos,
.widget-type-reddit,
.widget-type-bookmarks {
background-color: rgba(20, 50, 70, 0.35) !important; /* DARK BLUE, semi-transparent */
}
/* RSS feed items */
.rss-item,
.feed-item {
background-color: rgba(45, 126, 136, 0.25) !important; /* TEAL tint */
border-radius: 6px !important;
margin-bottom: 4px !important;
}
/* Video items */
.video-item,
.videos-item {
background-color: rgba(45, 126, 136, 0.25) !important; /* TEAL tint */
border-radius: 8px !important;
}
/* =========================================================================
WIDGET OVERLAYS
========================================================================= */
.widget.widget-type-iframe {
position: relative !important;
overflow: hidden !important;
border-radius: 12px !important;
}
.widget.widget-type-iframe iframe {
border-radius: 12px !important;
width: 100% !important;
border: 0 !important;
filter: sepia(0.25) saturate(1) hue-rotate(200deg) brightness(1.05) !important;
position: relative !important;
z-index: 1 !important;
}
/* Overlay ON TOP of iframe (you cant style inside cross-origin iframes) */
.widget.widget-type-iframe::after {
content: "";
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
border-radius: 12px;
background: rgba(45, 126, 255, 0.16);
}
/* =========================================================================
CONTAINER VERSIONS WIDGET HEIGHT INCREASE
========================================================================= */
.widget.widget-type-custom-api:has(.ver-widget) .ver-list {
max-height: 855px !important;
}
.widget.widget-type-custom-api:has(.ver-widget) {
min-height: 975px !important;
}
/* =========================================================================
WIDGET ROUNDED CORNERS
========================================================================= */
/* The parent widget container - round ALL corners and clip children */
.widget {
border-radius: 12px !important;
overflow: hidden !important; /* This clips the children to the rounded shape */
}
/* Header - round only TOP corners */
.widget-header {
border-top-left-radius: 12px !important;
border-top-right-radius: 12px !important;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
/* Content - round only BOTTOM corners */
.widget-content,
.widget-content:not(.widget-content-frameless),
.widget-content-frame {
border-top-left-radius: 0 !important;
border-top-right-radius: 0 !important;
border-bottom-left-radius: 12px !important;
border-bottom-right-radius: 12px !important;
}
/* For widgets without headers (content only), round all corners */
.widget:not(:has(.widget-header)) .widget-content {
border-radius: 12px !important;
}
/* =========================================================================
BOOKMARK STYLING
The link cards in bookmark widgets
========================================================================= */
.bookmark-link {
background-color: rgba(30, 70, 90, 0.6) !important; /* DARK BLUE-TEAL */
border-radius: 8px !important;
transition: background-color 0.2s ease !important;
}
.bookmark-link:hover {
background-color: rgba(45, 100, 120, 0.8) !important; /* LIGHTER on hover */
}
/* =========================================================================
REDDIT WIDGET SPECIFIC
Subreddit tabs and content
========================================================================= */
/* Subreddit tabs (R/HUNGARY, R/SELFHOSTED, etc.) */
.reddit-subreddit-tabs .tab,
.subreddit-tabs button,
.subreddit-tab,
[class*="subreddit"] button,
.widget-type-reddit button {
color: #5ac8d8 !important; /* CYAN */
}
.reddit-subreddit-tabs .tab:hover,
.subreddit-tabs button:hover,
[class*="subreddit"] button:hover {
color: #7ed9e6 !important; /* LIGHTER CYAN on hover */
}
/* Active subreddit tab */
.reddit-subreddit-tabs .tab.active,
.subreddit-tabs button.active,
.subreddit-tab.active,
[class*="subreddit"] button[aria-selected="true"] {
color: #7ed9e6 !important;
border-color: #5ac8d8 !important;
}
/* =========================================================================
VIDEO WIDGET SPECIFIC
YouTube video titles and channel names
========================================================================= */
.video-title,
.videos-item-title,
.video-channel,
.videos-item-channel {
color: #5ac8d8 !important;
}
/* =========================================================================
BACKGROUNDS - TRANSPARENT
Keep page backgrounds transparent to show the space background
========================================================================= */
.content-bounds,
.body-content,
.page,
#page-content,
.page-column,
.page-columns {
background: transparent !important;
}
/* =========================================================================
SCROLLBAR STYLING
Custom scrollbar colors
========================================================================= */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: rgba(30, 60, 90, 0.4); /* DARK BLUE track */
}
::-webkit-scrollbar-thumb {
background: rgba(90, 200, 216, 0.5); /* CYAN thumb */
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(90, 200, 216, 0.7); /* BRIGHTER CYAN on hover */
}
/* =========================================================================
EXPAND/COLLAPSE BUTTONS
"Show more" buttons in RSS, Reddit widgets
========================================================================= */
.expand-toggle-button,
.expand-toggle-button.container-expanded,
.widget-type-rss .expand-toggle-button {
background: transparent !important;
background-color: transparent !important;
box-shadow: none !important;
border: 0 !important;
}
.widget-type-rss .expand-toggle-button {
margin-top: 8px !important;
padding: 10px 12px !important;
border-top: 1px solid rgba(90, 200, 216, 0.2) !important; /* CYAN tinted border */
color: rgba(255,255,255,0.75) !important;
}
.expand-toggle-button:hover,
.widget-type-rss .expand-toggle-button:hover {
color: #5ac8d8 !important; /* CYAN on hover */
}
/* Remove pseudo-element backgrounds */
.expand-toggle-button::before,
.expand-toggle-button::after,
.expand-toggle-button-icon::before,
.expand-toggle-button-icon::after,
.widget-type-rss .expand-toggle-button::before,
.widget-type-rss .expand-toggle-button::after {
background: transparent !important;
background-color: transparent !important;
box-shadow: none !important;
border: 0 !important;
}
.expand-toggle-button.container-expanded {
backdrop-filter: none !important;
-webkit-backdrop-filter: none !important;
}
/* =========================================================================
IFRAME STYLING
Remove tint/overlay from embedded iframes
========================================================================= */
.widget.iframe-no-tint iframe {
filter: none !important;
}
.widget.iframe-no-tint::after {
content: none !important;
display: none !important;
}
/* =========================================================================
ADDITIONAL ELEMENTS
Various UI elements that might need color overrides
========================================================================= */
/* Monitor widget status indicators */
.monitor-site-status {
color: #5ac8d8 !important;
}
/* Group widget tabs */
.group-tabs button,
.tabs button {
color: #5ac8d8 !important;
}
.group-tabs button:hover,
.tabs button:hover {
color: #7ed9e6 !important;
}
.group-tabs button.active,
.tabs button.active,
.group-tabs button[aria-selected="true"],
.tabs button[aria-selected="true"] {
color: #7ed9e6 !important;
border-color: #5ac8d8 !important;
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: glance-kisfenyo
namespace: glance-system
labels:
app.kubernetes.io/name: glance-kisfenyo
app.kubernetes.io/instance: glance-kisfenyo
app.kubernetes.io/version: "v0.8.4"
annotations:
reloader.stakater.com/auto: "true"
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app.kubernetes.io/name: glance-kisfenyo
app.kubernetes.io/instance: glance-kisfenyo
template:
metadata:
labels:
app.kubernetes.io/name: glance-kisfenyo
app.kubernetes.io/instance: glance-kisfenyo
app.kubernetes.io/version: "v0.8.4"
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: glance
image: glanceapp/glance:v0.8.4
imagePullPolicy: IfNotPresent
env:
- name: TZ
value: "Europe/Budapest"
- name: PROMETHEUS_URL
value: "http://prometheus.mon-system.svc.cluster.local:9090"
- name: CRAFTY_URL
value: "https://crafty.dooplex.hu"
- name: CRAFTY_API_TOKEN
value: "newtoken"
- name: CRAFTY_SERVER_ID
value: "837c7378-7a76-46f7-b8ea-6f35c0a314f4"
- name: ROMM_URL
value: "arcade.dooplex.hu"
- name: UPTIME_KUMA_PUBLIC_URL
value: "http://uptimekuma.dooplex.hu"
- name: UPTIME_KUMA_URL
value: "http://uptimekuma.uptimekuma-system.svc.cluster.local:3001"
- name: UPTIME_KUMA_STATUS_SLUG
value: "homepage"
ports:
- name: http
containerPort: 8080
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: config
mountPath: /app/config/glance.yml
subPath: glance.yml
- name: config
mountPath: /app/config/assets/custom.css
subPath: custom.css
volumes:
- name: config
configMap:
name: glance-config-kisfenyo
---
apiVersion: v1
kind: Service
metadata:
name: glance-kisfenyo
namespace: glance-system
labels:
app.kubernetes.io/name: glance-kisfenyo
app.kubernetes.io/instance: glance-kisfenyo
spec:
type: ClusterIP
ports:
- name: http
port: 8080
targetPort: http
protocol: TCP
selector:
app.kubernetes.io/name: glance-kisfenyo
app.kubernetes.io/instance: glance-kisfenyo
---
# Ingress WITH Authentik proxy authentication
# Update the auth-url annotation with your actual outpost service name after creating in Authentik
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: glance-kisfenyo
namespace: glance-system
labels:
app.kubernetes.io/name: glance-kisfenyo
app.kubernetes.io/instance: glance-kisfenyo
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
external-dns.alpha.kubernetes.io/hostname: kisfenyo.dooplex.hu
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
nginx.ingress.kubernetes.io/proxy-busy-buffers-size: "32k"
# Authentik Forward Auth annotations
# TODO: Update 'glance-outpost' with your actual outpost name after creating in Authentik
nginx.ingress.kubernetes.io/auth-url: http://ak-outpost-glance-outpost.auth-system.svc.cluster.local:9000/outpost.goauthentik.io/auth/nginx
nginx.ingress.kubernetes.io/auth-signin: https://kisfenyo.dooplex.hu/outpost.goauthentik.io/start?rd=$escaped_request_uri
nginx.ingress.kubernetes.io/auth-response-headers: Set-Cookie,X-authentik-username,X-authentik-groups,X-authentik-email
nginx.ingress.kubernetes.io/auth-snippet: |
proxy_set_header X-Forwarded-Host $http_host;
spec:
ingressClassName: nginx-internal
rules:
- host: kisfenyo.dooplex.hu
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: glance-kisfenyo
port:
number: 8080
tls:
- hosts:
- kisfenyo.dooplex.hu
secretName: glance-kisfenyo-tls
---