NixOS configuration 🪄

✨🚧 continued with glance configuration

Signed-off-by: Xaiya Schumin <d.schumin@proton.me>

+342 -105
+342 -105
modules/nixos/services/glance.nix
··· 11 11 mkPackageOpt pkgs.glance "Glance configuration (Dashboard)"; 12 12 13 13 config = mkIf cfg.enable { 14 + age.secrets = { 15 + lastfm-apikey.rekeyFile = "${self}/secrets/lastfm-apikey.age"; 16 + tailscale-apikey.rekeyFile = "${self}/secrets/tailscale-apikey.age"; 17 + }; 18 + 14 19 services.glance = { 15 20 enable = true; 16 21 openFirewall = false; /* Managed through nginx server */ ··· 22 27 proxied = true; 23 28 }; 24 29 25 - pages = 26 - [ 27 - { 28 - name = "Overview"; 30 + pages = [ 31 + { 32 + name = "Overview"; 33 + hide-desktop-navigation = true; 34 + center-vertically = true; 29 35 30 - head-widgets = [ 36 + head-widgets = [ 37 + { 38 + type = "search"; 39 + autofocus = true; 40 + } 41 + ]; 42 + 43 + columns = [ 31 44 { 32 - type = "group"; 45 + size = "small"; 46 + widgets = [ 47 + { 48 + type = "clock"; 49 + hour-format = "24h"; 50 + } 51 + 52 + { 53 + type = "calendar"; 54 + } 55 + { 56 + type = "weather"; 57 + location = "Berlin, Germany"; 58 + hour-format = "24h"; 59 + } 60 + ]; 61 + } 62 + 63 + { 64 + size = "full"; 33 65 widgets = [ 34 66 { 35 67 type = "monitor"; ··· 57 89 url = "https://api.mcsrvstat.us/3/xaiya.dev"; 58 90 cache = "30s"; 59 91 template = '' 60 - <div style="display:flex; align-items:center; gap:12px;"> 61 - <div style="width:40px; height:40px; flex-shrink:0; border-radius:4px; display:flex; justify-content:center; align-items:center; overflow:hidden;"> 62 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" style="width:32px; height:32px; opacity:0.5;"> 63 - <path fill-rule="evenodd" d="M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0l-2.97 2.97ZM12 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" clip-rule="evenodd" /> 64 - </svg> 65 - </div> 92 + <div style="display:flex; align-items:center; gap:12px;"> 93 + <div style="width:40px; height:40px; flex-shrink:0; border-radius:4px; display:flex; justify-content:center; align-items:center; overflow:hidden;"> 94 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" style="width:32px; height:32px; opacity:0.5;"> 95 + <path fill-rule="evenodd" d="M1 5.25A2.25 2.25 0 0 1 3.25 3h13.5A2.25 2.25 0 0 1 19 5.25v9.5A2.25 2.25 0 0 1 16.75 17H3.25A2.25 2.25 0 0 1 1 14.75v-9.5Zm1.5 5.81v3.69c0 .414.336.75.75.75h13.5a.75.75 0 0 0 .75-.75v-2.69l-2.22-2.219a.75.75 0 0 0-1.06 0l-1.91 1.909.47.47a.75.75 0 1 1-1.06 1.06L6.53 8.091a.75.75 0 0 0-1.06 0l-2.97 2.97ZM12 7a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z" clip-rule="evenodd" /> 96 + </svg> 97 + </div> 66 98 67 - <div style="flex-grow:1; min-width:0;"> 68 - <a class="size-h4 block text-truncate color-highlight"> 69 - {{ .JSON.String "motd.raw.0" }} 70 - {{ if .JSON.Bool "online" }} 71 - <span 72 - style="width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-positive); display: inline-block; vertical-align: middle;" 73 - data-popover-type="text" 74 - data-popover-text="Online" 75 - ></span> 76 - {{ else }} 77 - <span 78 - style="width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-negative); display: inline-block; vertical-align: middle;" 79 - data-popover-type="text" 80 - data-popover-text="Offline" 81 - ></span> 82 - {{ end }} 83 - </a> 99 + <div style="flex-grow:1; min-width:0;"> 100 + <a class="size-h4 block text-truncate color-highlight"> 101 + {{ .JSON.String "motd.raw.0" }} 102 + {{ if .JSON.Bool "online" }} 103 + <span 104 + style="width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-positive); display: inline-block; vertical-align: middle;" 105 + data-popover-type="text" 106 + data-popover-text="Online" 107 + ></span> 108 + {{ else }} 109 + <span 110 + style="width: 8px; height: 8px; border-radius: 50%; background-color: var(--color-negative); display: inline-block; vertical-align: middle;" 111 + data-popover-type="text" 112 + data-popover-text="Offline" 113 + ></span> 114 + {{ end }} 115 + </a> 116 + 117 + <ul class="list-horizontal-text"> 118 + <li> 119 + {{ if .JSON.Bool "online" }} 120 + <span>{{ .JSON.String "version" }}</span> 121 + {{ else }} 122 + <span>Offline</span> 123 + {{ end }} 124 + </li> 125 + {{ if .JSON.Bool "online" }} 126 + <li data-popover-type="html"> 127 + <div data-popover-html> 128 + {{ range .JSON.Array "players.list" }}<br>{{ end }} 129 + </div> 130 + <p style="display:inline-flex;align-items:center;"> 131 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6" style="height:1em;vertical-align:middle;margin-right:0.5em;"> 132 + <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" /> 133 + </svg> 134 + {{ .JSON.Int "players.online" | formatNumber }}/{{ .JSON.Int "players.max" | formatNumber }} players 135 + </p> 136 + </li> 137 + {{ else }} 138 + <li> 139 + <p style="display:inline-flex;align-items:center;"> 140 + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6" style="height:1em;vertical-align:middle;margin-right:0.5em;opacity:0.5;"> 141 + <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" /> 142 + </svg> 143 + 0 players 144 + </p> 145 + </li> 146 + {{ end }} 147 + </ul> 148 + </div> 149 + </div> 150 + ''; 151 + } 152 + 153 + { 154 + type = "custom-api"; 155 + title = "Trending Repositories"; 156 + cache = "24h"; 157 + url = "https://api.ossinsight.io/v1/trends/repos/?period=past_24_hours&language=All"; 158 + template = '' 159 + <ul class="list list-gap-10 collapsible-container" data-collapse-after="2"> 160 + {{ range .JSON.Array "data.rows"}} 161 + <li> 162 + <a class="color-primary-if-not-visited" href="https://github.com/{{ .String "repo_name" }}">{{ .String "repo_name" }}</a> 163 + <ul class="list-horizontal-text"> 164 + <li class="color-highlight"> {{.String "primary_language"}} </li> 165 + 166 + <li style="display: flex; align-items: center;gap: 4px;"> 167 + {{ .Int "stars" }} 168 + <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true" focusable="false"> 169 + <path d="M3.612 15.443c-.386.198-.824-.149-.746-.592l.83-4.73L.173 6.765c-.329-.314-.158-.888.283-.95l4.898-.696L7.538.792c.197-.39.73-.39.927 0l2.184 4.327 4.898.696c.441.062.612.636.282.95l-3.522 3.356.83 4.73c.078.443-.36.79-.746.592L8 13.187l-4.389 2.256z"/> 170 + </svg> 171 + </li> 172 + 173 + <li style="display: flex; align-items: center;gap: 4px;"> 174 + {{ .Int "forks" }} 175 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> 176 + <path stroke="none" d="M0 0h24v24H0z" fill="none"/> 177 + <circle cx="12" cy="18" r="2" /> 178 + <circle cx="7" cy="6" r="2" /> 179 + <circle cx="17" cy="6" r="2" /> 180 + <path d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2" /> 181 + <path d="M12 12l0 4" /> 182 + </svg> 183 + </li> 184 + 185 + <li style="display: flex; align-items: center;gap: 4px;"> 186 + {{ .Int "pull_requests" }} 187 + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"> 188 + <circle cx="6" cy="6" r="3"></circle> 189 + <circle cx="18" cy="18" r="3"></circle> 190 + <path d="M13 6h3a2 2 0 0 1 2 2v7"></path> 191 + <line x1="6" y1="9" x2="6" y2="21"></line> 192 + </svg> 193 + </li> 194 + 195 + </ul> 196 + <ul class="list collapsible-container"> 197 + <li style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" class="color-subdue"> 198 + Contributors: {{ .String "contributor_logins"}} 199 + </li> 200 + <li> 201 + {{ .String "description" }} 202 + </li> 203 + </ul> 204 + </li> 205 + {{ end }} 206 + </ul> 207 + ''; 208 + } 209 + ]; 210 + } 211 + 212 + { 213 + size = "small"; 214 + widgets = [ 215 + { 216 + type = "custom-api"; 217 + "title" = "Recent listens"; 218 + "cache" = "60s"; 219 + "url" = "http://ws.audioscrobbler.com/2.0/"; 220 + "parameters" = { 221 + "method" = "user.getRecentTracks"; 222 + "user" = "xaiyadev"; 223 + "api_key" = "${(builtins.readFile config.age.secrets.lastfm-apikey.path)}"; 224 + "format" = "json"; 225 + "limit" = "5"; 226 + }; 227 + 228 + "template" = '' 229 + <ul class="list list-gap-10 collapsible-container" data-collapse-after="1"> 230 + {{ range .JSON.Array "recenttracks.track" }} 231 + <li class="flex items-center gap-10"> 232 + <img src={{ .String "image.2.#text" }} style="border-radius: 5px; min-width: 5rem; max-width: 5rem;" class="card"> 233 + <div class="flex-1"> 234 + <p class="color-positive size-h5">{{ .String "artist.#text" }}</p> 235 + <p class="size-h5">{{ .String "name" }}</p> 236 + <p class="size-h6"> 237 + {{ if .String "@attr.nowplaying" }} 238 + <span class="color-positive">Now playing</span> 239 + {{ else }} 240 + <span class="color-subdue" {{ .String "date.#text" | parseRelativeTime "02 Jan 2006, 15:04" }}></span> 241 + {{ end }} 242 + </p> 243 + </div> 244 + </li> 245 + {{ end }} 246 + </ul> 247 + ''; 248 + 249 + } 84 250 85 - <ul class="list-horizontal-text"> 86 - <li> 87 - {{ if .JSON.Bool "online" }} 88 - <span>{{ .JSON.String "version" }}</span> 89 - {{ else }} 90 - <span>Offline</span> 91 - {{ end }} 92 - </li> 93 - {{ if .JSON.Bool "online" }} 94 - <li data-popover-type="html"> 95 - <div data-popover-html> 96 - {{ range .JSON.Array "players.list" }}<br>{{ end }} 97 - </div> 98 - <p style="display:inline-flex;align-items:center;"> 99 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6" style="height:1em;vertical-align:middle;margin-right:0.5em;"> 100 - <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" /> 101 - </svg> 102 - {{ .JSON.Int "players.online" | formatNumber }}/{{ .JSON.Int "players.max" | formatNumber }} players 103 - </p> 104 - </li> 105 - {{ else }} 106 - <li> 107 - <p style="display:inline-flex;align-items:center;"> 108 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6" style="height:1em;vertical-align:middle;margin-right:0.5em;opacity:0.5;"> 109 - <path fill-rule="evenodd" d="M7.5 6a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM3.751 20.105a8.25 8.25 0 0 1 16.498 0 .75.75 0 0 1-.437.695A18.683 18.683 0 0 1 12 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 0 1-.437-.695Z" clip-rule="evenodd" /> 110 - </svg> 111 - 0 players 112 - </p> 113 - </li> 114 - {{ end }} 115 - </ul> 116 - </div> 117 - </div> 118 - ''; 119 - } 251 + { 252 + type = "releases"; 253 + show-source-icon = true; 254 + repositories = [ 255 + "WillPower3309/swayfx" 256 + "Inrixia/TidaLuna" 120 257 ]; 121 - } 122 - ]; 258 + } 259 + 260 + { 261 + type = "server-stats"; 262 + servers = [ 263 + { 264 + type = "local"; 265 + name = "Apricot"; 266 + 267 + mountpoints = { "/mnt/raid" = { name = "/mnt/raid"; }; }; 268 + } 269 + ]; 270 + } 271 + 272 + { 273 + type = "custom-api"; 274 + title = "Tailscale devices"; 275 + title-url = "https://login.tailscale.com/admin/machines"; 276 + url = "https://api.tailscale.com/api/v2/tailnet/-/devices"; 277 + headers = { 278 + Authorization = "Bearer ${(builtins.readFile config.age.secrets.lastfm-apikey.path)}"; 279 + }; 280 + cache = "10m"; 281 + template = '' 282 + {{/* User Variables */}} 283 + {{/* Set to true if you'd like an indicator for online devices */}} 284 + {{ $enableOnlineIndicator := true }} 285 + 286 + <style> 287 + .device-info-container { 288 + position: relative; 289 + overflow: hidden; 290 + height: 1.5em; 291 + } 292 + 293 + .device-info { 294 + display: flex; 295 + transition: transform 0.2s ease, opacity 0.2s ease; 296 + } 297 + 298 + .device-ip { 299 + position: absolute; 300 + top: 0; 301 + left: 0; 302 + transform: translateY(-100%); 303 + opacity: 0; 304 + transition: transform 0.2s ease, opacity 0.2s ease; 305 + } 306 + 307 + .device-info-container:hover .device-info { 308 + transform: translateY(100%); 309 + opacity: 0; 310 + } 311 + 312 + .device-info-container:hover .device-ip { 313 + transform: translateY(0); 314 + opacity: 1; 315 + } 316 + 317 + .update-indicator { 318 + width: 8px; 319 + height: 8px; 320 + border-radius: 50%; 321 + background-color: var(--color-primary); 322 + display: inline-block; 323 + margin-left: 4px; 324 + vertical-align: middle; 325 + } 326 + 327 + .offline-indicator { 328 + width: 8px; 329 + height: 8px; 330 + border-radius: 50%; 331 + background-color: var(--color-negative); 332 + display: inline-block; 333 + margin-left: 4px; 334 + vertical-align: middle; 335 + } 123 336 124 - columns = [ 125 - { 126 - size = "full"; 127 - widgets = [ 128 - { 129 - type = "calendar"; 130 - } 131 - { 132 - type = "weather"; 133 - location = "Berlin, Germany"; 134 - hour-format = "24h"; 135 - } 136 - { 137 - type = "releases"; 138 - show-source-icon = true; 139 - repositories = [ 140 - "WillPower3309/swayfx" 141 - "Inrixia/TidaLuna" 142 - ]; 143 - } 144 - { 145 - type = "server-stats"; 146 - servers = [ 147 - { 148 - type = "local"; 149 - name = "Apricot"; 337 + .online-indicator { 338 + width: 8px; 339 + height: 8px; 340 + border-radius: 50%; 341 + background-color: var(--color-positive); 342 + display: inline-block; 343 + margin-left: 4px; 344 + vertical-align: middle; 345 + } 150 346 151 - mountpoints = { "/mnt/raid" = { name = "/mnt/raid"; }; }; 152 - } 153 - ]; 154 - } 347 + .device-name-container { 348 + display: flex; 349 + align-items: center; 350 + gap: 8px; 351 + } 352 + 353 + .indicators-container { 354 + display: flex; 355 + align-items: center; 356 + gap: 4px; 357 + } 358 + </style> 359 + <ul class="list list-gap-10 collapsible-container" data-collapse-after="4"> 360 + {{ range .JSON.Array "devices" }} 361 + <li> 362 + <div class="flex items-center gap-10"> 363 + <div class="device-name-container grow"> 364 + <span class="size-h4 block text-truncate color-primary"> 365 + {{ findMatch "^([^.]+)" (.String "name") }} 366 + </span> 367 + <div class="indicators-container"> 368 + {{ if (.Bool "updateAvailable") }} 369 + <span class="update-indicator" data-popover-type="text" data-popover-text="Update Available"></span> 370 + {{ end }} 155 371 156 - { 157 - type = "clock"; 158 - hour-format = "24h"; 159 - } 160 - ]; 161 - } 162 - ]; 163 - } 164 - ]; 372 + {{ $lastSeen := .String "lastSeen" | parseTime "rfc3339" }} 373 + {{ if not ($lastSeen.After (offsetNow "-10s")) }} 374 + {{ $lastSeenTimezoned := $lastSeen.In now.Location }} 375 + <span class="offline-indicator" data-popover-type="text" 376 + data-popover-text="Offline - Last seen {{ $lastSeenTimezoned.Format " Jan 2 3:04pm" }}"></span> 377 + {{ else if $enableOnlineIndicator }} 378 + <span class="online-indicator" data-popover-type="text" data-popover-text="Online"></span> 379 + {{ end }} 380 + </div> 381 + </div> 382 + </div> 383 + <div class="device-info-container"> 384 + <ul class="list-horizontal-text device-info"> 385 + <li>{{ .String "os" }}</li> 386 + <li>{{ .String "user" }}</li> 387 + </ul> 388 + <div class="device-ip"> 389 + {{ .String "addresses.0"}} 390 + </div> 391 + </div> 392 + </li> 393 + {{ end }} 394 + </ul> 395 + ''; 396 + } 397 + ]; 398 + } 399 + ]; 400 + } 401 + ]; 165 402 166 403 /* rose-pine theme */ 167 404 theme = {