initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
48
assets/global.css
Normal file
48
assets/global.css
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap');
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--color-info: hsl(43, 50%, 70%);
|
||||||
|
--color-warning: hsl(43, 50%, 70%);
|
||||||
|
/* Widget background color with alpha */
|
||||||
|
--bga: 70%;
|
||||||
|
--color-widget-background: hsl(
|
||||||
|
var(--color-widget-background-hsl-values),
|
||||||
|
var(--bga)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: 'Outfit', sans-serif;
|
||||||
|
background-image: url(https://images.pexels.com/photos/2098428/pexels-photo-2098428.jpeg);
|
||||||
|
/* backdrop-filter: blur(2px); */
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
/* Blur effect for widget backgrounds */
|
||||||
|
.widget-content:not(.widget-content-frameless),
|
||||||
|
.widget-content-frame {
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
box-shadow:
|
||||||
|
rgba(0, 0, 0, 0.02) 0px 1px 3px 0px,
|
||||||
|
rgba(27, 31, 35, 0.15) 0px 0px 0px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make widgets same height */
|
||||||
|
.widget-type-dns-stats {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.widget-type-dns-stats .widget-content {
|
||||||
|
height: calc(100% - var(--widget-gap));
|
||||||
|
}
|
||||||
|
.widget-type-dns-stats .widget-content .dns-stats {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-around;
|
||||||
|
}
|
||||||
|
.widget-type-prowlarr-custom .widget-content {
|
||||||
|
height: calc(100% - var(--widget-gap));
|
||||||
|
}
|
||||||
BIN
assets/images/cloudflare.png
Normal file
BIN
assets/images/cloudflare.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
33
config/bookmarks.yml
Normal file
33
config/bookmarks.yml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
- type: bookmarks
|
||||||
|
groups:
|
||||||
|
- links:
|
||||||
|
- title: Gmail
|
||||||
|
url: https://mail.google.com/mail/u/0/
|
||||||
|
- title: Amazon
|
||||||
|
url: https://www.amazon.com/
|
||||||
|
- title: Github
|
||||||
|
url: https://github.com/
|
||||||
|
- title: Wikipedia
|
||||||
|
url: https://en.wikipedia.org/
|
||||||
|
- title: Entertainment
|
||||||
|
color: 10 70 50
|
||||||
|
links:
|
||||||
|
- title: Netflix
|
||||||
|
url: https://www.netflix.com/
|
||||||
|
- title: Disney+
|
||||||
|
url: https://www.disneyplus.com/
|
||||||
|
- title: YouTube
|
||||||
|
url: https://www.youtube.com/
|
||||||
|
- title: Prime Video
|
||||||
|
url: https://www.primevideo.com/
|
||||||
|
- title: Social
|
||||||
|
color: 200 50 50
|
||||||
|
links:
|
||||||
|
- title: Reddit
|
||||||
|
url: https://www.reddit.com/
|
||||||
|
- title: Twitter
|
||||||
|
url: https://twitter.com/
|
||||||
|
- title: Instagram
|
||||||
|
url: https://www.instagram.com/
|
||||||
|
|
||||||
|
|
||||||
78
config/glance.yml
Normal file
78
config/glance.yml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
server:
|
||||||
|
assets-path: /app/assets
|
||||||
|
|
||||||
|
branding:
|
||||||
|
hide-footer: true
|
||||||
|
|
||||||
|
theme:
|
||||||
|
background-color: 203 27 9
|
||||||
|
contrast-multiplier: 1.2
|
||||||
|
primary-color: 165 78 51
|
||||||
|
positive-color: 165 78 51
|
||||||
|
negative-color: 360 100 71
|
||||||
|
disable-picker: true
|
||||||
|
custom-css-file: /global.css
|
||||||
|
|
||||||
|
pages:
|
||||||
|
- name: Home
|
||||||
|
# Optionally, if you only have a single page you can hide the desktop navigation for a cleaner look
|
||||||
|
hide-desktop-navigation: true
|
||||||
|
columns:
|
||||||
|
- size: small
|
||||||
|
widgets:
|
||||||
|
- type: calendar
|
||||||
|
hide-header: true
|
||||||
|
first-day-of-week: sunday
|
||||||
|
|
||||||
|
- $include: bookmarks.yml
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- size: full
|
||||||
|
widgets:
|
||||||
|
- type: split-column
|
||||||
|
max-columns: 4
|
||||||
|
widgets:
|
||||||
|
- $include: plausible.yml
|
||||||
|
css-class: widget-type-plausible-prowlarr-custom
|
||||||
|
- $include: uptime.yml
|
||||||
|
css-class: widget-type-uptime-custom
|
||||||
|
- $include: services.yml
|
||||||
|
- $include: raindrop.yml
|
||||||
|
css-class: widget-type-raindrop-custom
|
||||||
|
|
||||||
|
|
||||||
|
- size: small
|
||||||
|
widgets:
|
||||||
|
- type: clock
|
||||||
|
hide-header: true
|
||||||
|
time-format: 24h
|
||||||
|
date-format: d MMMM yyyy
|
||||||
|
show-seconds: true
|
||||||
|
show-timezone: true
|
||||||
|
timezone: Europe/Istanbul
|
||||||
|
|
||||||
|
- type: weather
|
||||||
|
hide-header: true
|
||||||
|
location: ${WEATHER_LOCATION}
|
||||||
|
units: metric
|
||||||
|
hour-format: 24h
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Add more pages here:
|
||||||
|
# - name: Your page name
|
||||||
|
# columns:
|
||||||
|
# - size: small
|
||||||
|
# widgets:
|
||||||
|
# # Add widgets here
|
||||||
|
|
||||||
|
# - size: full
|
||||||
|
# widgets:
|
||||||
|
# # Add widgets here
|
||||||
|
|
||||||
|
# - size: small
|
||||||
|
# widgets:
|
||||||
|
# # Add widgets here
|
||||||
343
config/media-server.yml
Normal file
343
config/media-server.yml
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
- type: custom-api
|
||||||
|
title: Media Server History
|
||||||
|
frameless: true
|
||||||
|
cache: 5m
|
||||||
|
options:
|
||||||
|
media-server: '${MEDIA_SERVER_TYPE}'
|
||||||
|
base-url: ${JELLYFIN_URL}
|
||||||
|
api-key: ${JELLYFIN_TOKEN}
|
||||||
|
user-name: ${JELLYFIN_USER}
|
||||||
|
media-types: '${MEDIA_SERVER_TYPES}'
|
||||||
|
history-length: '${MEDIA_SERVER_HISTORY_LENGTH}'
|
||||||
|
small-column: true
|
||||||
|
compact: true
|
||||||
|
show-thumbnail: true
|
||||||
|
thumbnail-aspect-ratio: 'default'
|
||||||
|
show-user: false
|
||||||
|
time-absolute: false
|
||||||
|
time-format: 'Jan 02 15:04'
|
||||||
|
template: |
|
||||||
|
{{ $mediaServer := .Options.StringOr "media-server" "" }}
|
||||||
|
{{ $baseURL := .Options.StringOr "base-url" "" }}
|
||||||
|
{{ $apiKey := .Options.StringOr "api-key" "" }}
|
||||||
|
{{ $userName := .Options.StringOr "user-name" "" }}
|
||||||
|
|
||||||
|
{{ define "errorMsg" }}
|
||||||
|
<div class="widget-error-header">
|
||||||
|
<div class="color-negative size-h3">ERROR</div>
|
||||||
|
<svg class="widget-error-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126ZM12 15.75h.007v.008H12v-.008Z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<p class="break-all">{{ . }}</p>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if or
|
||||||
|
(eq $mediaServer "")
|
||||||
|
(eq $baseURL "")
|
||||||
|
(eq $apiKey "")
|
||||||
|
(and (eq $mediaServer "jellyfin") (eq $userName ""))
|
||||||
|
}}
|
||||||
|
{{ template "errorMsg" "Some required options are not set" }}
|
||||||
|
{{ else }}
|
||||||
|
|
||||||
|
{{ $historyLength := .Options.StringOr "history-length" "10" }}
|
||||||
|
{{ $mediaTypes := .Options.StringOr "media-types" "" }}
|
||||||
|
{{ if eq $mediaServer "tautulli" }}
|
||||||
|
{{ $mediaTypes = .Options.StringOr "media-types" "movie,episode,track" }}
|
||||||
|
{{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
|
||||||
|
{{ $mediaTypes = .Options.StringOr "media-types" "Movie,Episode,Audio" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $isSmallColumn := .Options.BoolOr "small-column" false }}
|
||||||
|
{{ $isCompact := .Options.BoolOr "compact" true }}
|
||||||
|
{{ $showThumbnail := .Options.BoolOr "show-thumbnail" false }}
|
||||||
|
{{ $thumbAspectRatio := .Options.StringOr "thumbnail-aspect-ratio" "" }}
|
||||||
|
{{ $showUser := .Options.BoolOr "show-user" true }}
|
||||||
|
{{ $timeAbsolute := .Options.BoolOr "time-absolute" false }}
|
||||||
|
{{ $timeFormat := .Options.StringOr "time-format" "Jan 02 15:04" }}
|
||||||
|
|
||||||
|
{{ $userID := "" }}
|
||||||
|
{{ $historyRequestURL := "" }}
|
||||||
|
{{ $usersRequestURL := "" }}
|
||||||
|
{{ $historyCall := "" }}
|
||||||
|
{{ $usersCall := "" }}
|
||||||
|
{{ $history := "" }}
|
||||||
|
{{ $users := "" }}
|
||||||
|
|
||||||
|
{{ if eq $mediaServer "plex" }}
|
||||||
|
{{ $historyRequestURL = concat $baseURL "/status/sessions/history/all" }}
|
||||||
|
{{ $historyCall = newRequest $historyRequestURL
|
||||||
|
| withParameter "limit" $historyLength
|
||||||
|
| withParameter "sort" "viewedAt:desc"
|
||||||
|
| withHeader "Accept" "application/json"
|
||||||
|
| withHeader "X-Plex-Token" $apiKey
|
||||||
|
| getResponse }}
|
||||||
|
|
||||||
|
{{ if $historyCall.JSON.Exists "MediaContainer" }}
|
||||||
|
{{ $history = $historyCall.JSON.Array "MediaContainer.Metadata" }}
|
||||||
|
{{ else }}
|
||||||
|
{{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $usersRequestURL = concat $baseURL "/accounts" }}
|
||||||
|
{{ $usersCall = newRequest $usersRequestURL
|
||||||
|
| withHeader "Accept" "application/json"
|
||||||
|
| withHeader "X-Plex-Token" $apiKey
|
||||||
|
| getResponse }}
|
||||||
|
{{ $users = $usersCall.JSON.Array "MediaContainer.Account" }}
|
||||||
|
|
||||||
|
{{ else if eq $mediaServer "tautulli" }}
|
||||||
|
{{ $historyRequestURL = concat $baseURL "/api/v2" }}
|
||||||
|
{{ $historyCall = newRequest $historyRequestURL
|
||||||
|
| withParameter "apikey" $apiKey
|
||||||
|
| withParameter "cmd" "get_history"
|
||||||
|
| withParameter "length" $historyLength
|
||||||
|
| withParameter "media_type" $mediaTypes
|
||||||
|
| withHeader "Accept" "application/json"
|
||||||
|
| getResponse }}
|
||||||
|
|
||||||
|
{{ if eq $historyCall.Response.StatusCode 200 }}
|
||||||
|
{{ $history = $historyCall.JSON.Array "response.data.data" }}
|
||||||
|
{{ else }}
|
||||||
|
{{ template "errorMsg" (concat "Could not fetch " $mediaServer " API.") }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
|
||||||
|
{{ $usersRequestURL = concat $baseURL "/Users" }}
|
||||||
|
{{ $usersCall = newRequest $usersRequestURL
|
||||||
|
| withParameter "api_key" $apiKey
|
||||||
|
| withHeader "Accept" "application/json"
|
||||||
|
| getResponse }}
|
||||||
|
|
||||||
|
{{ $usersList := $usersCall.JSON.Array "" }}
|
||||||
|
{{ range $i, $user := $usersList }}
|
||||||
|
{{ if eq ($user.String "Name") $userName }}
|
||||||
|
{{ $userID = $user.String "Id" }}
|
||||||
|
{{ break }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if eq $userID "" }}
|
||||||
|
{{ template "errorMsg" (concat "User '" $userName "' not found.") }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $historyRequestURL = concat $baseURL "/Users/" $userID "/Items" }}
|
||||||
|
{{ $historyCall = newRequest $historyRequestURL
|
||||||
|
| withParameter "api_key" $apiKey
|
||||||
|
| withParameter "Limit" $historyLength
|
||||||
|
| withParameter "IncludeItemTypes" $mediaTypes
|
||||||
|
| withParameter "Recursive" "true"
|
||||||
|
| withParameter "isPlayed" "true"
|
||||||
|
| withParameter "sortBy" "DatePlayed"
|
||||||
|
| withParameter "sortOrder" "Descending"
|
||||||
|
| withParameter "Fields" "UserDataLastPlayedDate"
|
||||||
|
| withHeader "Accept" "application/json"
|
||||||
|
| getResponse }}
|
||||||
|
|
||||||
|
{{ $history = $historyCall.JSON.Array "Items" }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if and (eq $historyCall.Response.StatusCode 200) (eq (len $history) 0) }}
|
||||||
|
<p>Nothing has been played. Start streaming something!</p>
|
||||||
|
{{ else }}
|
||||||
|
<div class="carousel-container show-right-cutoff">
|
||||||
|
<div class="cards-horizontal carousel-items-container">
|
||||||
|
{{ range $n, $item := $history }}
|
||||||
|
{{ $mediaType := "" }}
|
||||||
|
{{ $isMovie := false }}
|
||||||
|
{{ $isShows := false }}
|
||||||
|
{{ $isMusic := false }}
|
||||||
|
{{ $title := "" }}
|
||||||
|
{{ $showTitle := "" }}
|
||||||
|
{{ $showSeason := "" }}
|
||||||
|
{{ $showEpisode := "" }}
|
||||||
|
{{ $artist := "" }}
|
||||||
|
{{ $albumTitle := "" }}
|
||||||
|
{{ $thumbURL := "" }}
|
||||||
|
{{ $playedAt := "" }}
|
||||||
|
|
||||||
|
{{ if eq $mediaServer "plex" }}
|
||||||
|
{{ $userID = $item.Int "accountID" }}
|
||||||
|
{{ range $n, $u := $users }}
|
||||||
|
{{ if eq $userID ($u.Int "id") }}
|
||||||
|
{{ $userName = $u.String "name" }}
|
||||||
|
{{ break }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $mediaType = $item.String "type" }}
|
||||||
|
{{ $isMovie = eq $mediaType "movie" }}
|
||||||
|
{{ $isShows = eq $mediaType "episode" }}
|
||||||
|
{{ $isMusic = eq $mediaType "track" }}
|
||||||
|
|
||||||
|
{{ $title = $item.String "title" }}
|
||||||
|
{{ if $isShows }}
|
||||||
|
{{ $showTitle = $item.String "grandparentTitle" }}
|
||||||
|
{{ $showSeason = $item.String "parentIndex" }}
|
||||||
|
{{ $showEpisode = $item.String "index" }}
|
||||||
|
{{ else if $isMusic }}
|
||||||
|
{{ $artist = $item.String "grandparentTitle" }}
|
||||||
|
{{ $albumTitle = $item.String "parentTitle" }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $thumbID := $item.String "thumb" }}
|
||||||
|
{{ if or $isShows $isMusic}}
|
||||||
|
{{ $thumbID = $item.String "parentThumb" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $thumbURL = concat $baseURL $thumbID "?X-Plex-Token=" $apiKey }}
|
||||||
|
|
||||||
|
{{ $time := $item.String "viewedAt" }}
|
||||||
|
{{ if $timeAbsolute }}
|
||||||
|
{{ $playedAt = $time | parseLocalTime "unix" | formatTime $timeFormat }}
|
||||||
|
{{ else }}
|
||||||
|
{{ $playedAt = $time | parseRelativeTime "unix" }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ else if eq $mediaServer "tautulli" }}
|
||||||
|
{{ $userName = $item.String "user" }}
|
||||||
|
{{ $mediaType = $item.String "media_type" }}
|
||||||
|
{{ $isMovie = eq $mediaType "movie" }}
|
||||||
|
{{ $isShows = eq $mediaType "episode" }}
|
||||||
|
{{ $isMusic = eq $mediaType "track" }}
|
||||||
|
|
||||||
|
{{ $title = $item.String "title" }}
|
||||||
|
{{ if $isShows }}
|
||||||
|
{{ $showTitle = $item.String "grandparent_title" }}
|
||||||
|
{{ $showSeason = $item.String "parent_media_index" }}
|
||||||
|
{{ $showEpisode = $item.String "media_index" }}
|
||||||
|
{{ else if $isMusic }}
|
||||||
|
{{ $artist = $item.String "grandparent_title" }}
|
||||||
|
{{ $albumTitle = $item.String "parent_title" }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $thumbID := $item.String "thumb" }}
|
||||||
|
{{ $thumbURL = concat $baseURL "/api/v2?apikey=" $apiKey "&cmd=pms_image_proxy&img=" $thumbID }}
|
||||||
|
|
||||||
|
{{ $time := $item.String "date" }}
|
||||||
|
{{ if $timeAbsolute }}
|
||||||
|
{{ $playedAt = $time | parseLocalTime "unix" | formatTime $timeFormat }}
|
||||||
|
{{ else }}
|
||||||
|
{{ $playedAt = $time | parseRelativeTime "unix" }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ else if or (eq $mediaServer "jellyfin") (eq $mediaServer "emby") }}
|
||||||
|
{{ $mediaType = $item.String "Type" }}
|
||||||
|
{{ $isMovie = eq $mediaType "Movie" }}
|
||||||
|
{{ $isShows = eq $mediaType "Episode" }}
|
||||||
|
{{ $isMusic = eq $mediaType "Audio" }}
|
||||||
|
|
||||||
|
{{ $title = $item.String "Name" }}
|
||||||
|
{{ if $isShows }}
|
||||||
|
{{ $showTitle = $item.String "SeriesName" }}
|
||||||
|
{{ $showSeason = $item.String "ParentIndexNumber" }}
|
||||||
|
{{ $showEpisode = $item.String "IndexNumber" }}
|
||||||
|
{{ else if $isMusic }}
|
||||||
|
{{ $artist = $item.String "AlbumArtist" }}
|
||||||
|
{{ $albumTitle = $item.String "Album" }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $thumbID := $item.String "Id" }}
|
||||||
|
{{ if $isShows }}
|
||||||
|
{{ $thumbID = $item.String "SeasonId" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $thumbURL = concat $baseURL "/Items/" $thumbID "/Images/Primary?api_key=" $apiKey }}
|
||||||
|
|
||||||
|
{{ $time := $item.String "UserData.LastPlayedDate" }}
|
||||||
|
{{ if $timeAbsolute }}
|
||||||
|
{{ $playedAt = $time | parseLocalTime "rfc3339" | formatTime $timeFormat }}
|
||||||
|
{{ else }}
|
||||||
|
{{ $playedAt = $time | parseRelativeTime "rfc3339" }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $showInfoFormat := concat "Season " $showSeason " Episode " $showEpisode}}
|
||||||
|
{{ if $isCompact }}
|
||||||
|
{{ $showInfoFormat = concat "S" $showSeason "E" $showEpisode}}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<div class="card widget-content-frame">
|
||||||
|
{{ if $showThumbnail }}
|
||||||
|
<img src="{{ $thumbURL | safeURL }}"
|
||||||
|
alt="{{ $title }} thumbnail"
|
||||||
|
loading="lazy"
|
||||||
|
class="media-server-thumbnail shrink-0"
|
||||||
|
style="
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--border-radius) var(--border-radius) 0 0;
|
||||||
|
{{ if eq $thumbAspectRatio "square" }}
|
||||||
|
aspect-ratio: 1;
|
||||||
|
{{ else if eq $thumbAspectRatio "portrait" }}
|
||||||
|
aspect-ratio: 3/4;
|
||||||
|
{{ else if eq $thumbAspectRatio "landscape" }}
|
||||||
|
aspect-ratio: 4/3;
|
||||||
|
{{ else }}
|
||||||
|
aspect-ratio: initial;
|
||||||
|
{{ end }}
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<div class="grow padding-inline-widget margin-top-10 margin-bottom-10">
|
||||||
|
<ul class="flex flex-column justify-evenly margin-bottom-3 {{if $isSmallColumn}}size-h6{{end}}" style="height: 100%;">
|
||||||
|
{{ if $isCompact }}
|
||||||
|
<ul class="list-horizontal-text flex-nowrap">
|
||||||
|
{{ if $showUser }}
|
||||||
|
<li class="color-primary text-truncate">{{ $userName }}</li>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if $timeAbsolute }}
|
||||||
|
<li class="text-truncate">{{ $playedAt }}</li>
|
||||||
|
{{ else }}
|
||||||
|
<li class="shrink-0">
|
||||||
|
<span {{ $playedAt }}></span>
|
||||||
|
{{ if not $showUser }}
|
||||||
|
<span> ago</span>
|
||||||
|
{{ end }}
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{{ if $isShows }}
|
||||||
|
<ul class="list-horizontal-text flex-nowrap">
|
||||||
|
<li class="text-truncate">{{ $showInfoFormat }}</li>
|
||||||
|
<li class="text-truncate">{{ $showTitle }}</li>
|
||||||
|
</ul>
|
||||||
|
{{ else if $isMusic }}
|
||||||
|
<ul class="list-horizontal-text flex-nowrap">
|
||||||
|
<li class="text-truncate">{{ $artist }}</li>
|
||||||
|
<li class="text-truncate">{{ $albumTitle }}</li>
|
||||||
|
</ul>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<li class="text-truncate">{{ $title }}</li>
|
||||||
|
{{ else }}
|
||||||
|
{{ if $showUser }}
|
||||||
|
<li class="color-primary text-truncate">{{ $userName }}</li>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if $timeAbsolute }}
|
||||||
|
<li class="text-truncate">{{ $playedAt }}</li>
|
||||||
|
{{ else }}
|
||||||
|
<li class="text-truncate">
|
||||||
|
<span {{ $playedAt }}></span>
|
||||||
|
<span> ago</span>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ if $isShows }}
|
||||||
|
<li class="text-truncate">{{ $showTitle }}</li>
|
||||||
|
<li class="text-truncate">{{ $showInfoFormat }}</li>
|
||||||
|
{{ else if $isMusic }}
|
||||||
|
<li class="text-truncate">{{ $artist }}</li>
|
||||||
|
<li class="text-truncate">{{ $albumTitle }}</li>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
<li class="text-truncate">{{ $title }}</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
260
config/plausible.yml
Normal file
260
config/plausible.yml
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
- type: custom-api
|
||||||
|
title: Site Statistics
|
||||||
|
hide-header: true
|
||||||
|
cache: 1m
|
||||||
|
subrequests:
|
||||||
|
# aveminakarabudak.com
|
||||||
|
a1:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/realtime/visitors?site_id=aveminakarabudak.com
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
s1:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/aggregate?site_id=aveminakarabudak.com&period=day&metrics=visitors,pageviews,visits
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
# ayrisapart.com
|
||||||
|
a2:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/realtime/visitors?site_id=ayrisapart.com
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
s2:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/aggregate?site_id=ayrisapart.com&period=day&metrics=visitors,pageviews,visits
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
# ayris.tech
|
||||||
|
a3:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/realtime/visitors?site_id=ayris.tech
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
s3:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/aggregate?site_id=ayris.tech&period=day&metrics=visitors,pageviews,visits
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
# irisiptv.online
|
||||||
|
a4:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/realtime/visitors?site_id=irisiptv.online
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
s4:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/aggregate?site_id=irisiptv.online&period=day&metrics=visitors,pageviews,visits
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
# screencapr.com
|
||||||
|
a5:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/realtime/visitors?site_id=screencapr.com
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
s5:
|
||||||
|
url: ${PLAUSIBLE_BASE_URL}/api/v1/stats/aggregate?site_id=screencapr.com&period=day&metrics=visitors,pageviews,visits
|
||||||
|
headers: { Authorization: "Bearer ${PLAUSIBLE_TOKEN}" }
|
||||||
|
template: |
|
||||||
|
<div class="plausible-multi-site">
|
||||||
|
<style>
|
||||||
|
.plausible-site-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--color-widget-content-border);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.plausible-site-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.site-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.site-icon img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.site-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.site-domain {
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--color-highlight);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
.site-metrics {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-subdue);
|
||||||
|
font-family: var(--font-family-mono);
|
||||||
|
}
|
||||||
|
.metric-item b {
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
.realtime-badge {
|
||||||
|
background: rgba(34, 197, 94, 0.15);
|
||||||
|
color: #22c55e;
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
.realtime-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: currentColor;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: pulse 1.5s infinite;
|
||||||
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { opacity: 1; transform: scale(1); }
|
||||||
|
50% { opacity: 0.4; transform: scale(1.2); }
|
||||||
|
100% { opacity: 1; transform: scale(1); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{{ $sites := . }}
|
||||||
|
|
||||||
|
<!-- Site Block 1 -->
|
||||||
|
{{ $a1 := .Subrequest "a1" }}
|
||||||
|
{{ $s1 := .Subrequest "s1" }}
|
||||||
|
<div class="plausible-site-row">
|
||||||
|
<div class="site-icon">
|
||||||
|
<img src="https://icon.horse/icon/aveminakarabudak.com" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="site-info">
|
||||||
|
<span class="site-domain">aveminakarabudak.com</span>
|
||||||
|
<div class="site-metrics">
|
||||||
|
{{ if eq $s1.Response.StatusCode 200 }}
|
||||||
|
<span class="metric-item">Visitors: <b>{{ $s1.JSON.Get "results.visitors.value" }}</b></span>
|
||||||
|
<span class="metric-item">Views: <b>{{ $s1.JSON.Get "results.pageviews.value" }}</b></span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-status">
|
||||||
|
{{ if eq $a1.Response.StatusCode 200 }}
|
||||||
|
{{ $c1 := $a1.JSON.Raw }}
|
||||||
|
{{ if ne $c1 "0" }}
|
||||||
|
<div class="realtime-badge">
|
||||||
|
<span class="realtime-dot"></span>
|
||||||
|
{{ $c1 }} Active
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Site Block 2 -->
|
||||||
|
{{ $a2 := .Subrequest "a2" }}
|
||||||
|
{{ $s2 := .Subrequest "s2" }}
|
||||||
|
<div class="plausible-site-row">
|
||||||
|
<div class="site-icon">
|
||||||
|
<img src="https://icon.horse/icon/ayrisapart.com" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="site-info">
|
||||||
|
<span class="site-domain">ayrisapart.com</span>
|
||||||
|
<div class="site-metrics">
|
||||||
|
{{ if eq $s2.Response.StatusCode 200 }}
|
||||||
|
<span class="metric-item">Visitors: <b>{{ $s2.JSON.Get "results.visitors.value" }}</b></span>
|
||||||
|
<span class="metric-item">Views: <b>{{ $s2.JSON.Get "results.pageviews.value" }}</b></span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-status">
|
||||||
|
{{ if eq $a2.Response.StatusCode 200 }}
|
||||||
|
{{ $c2 := $a2.JSON.Raw }}
|
||||||
|
{{ if ne $c2 "0" }}
|
||||||
|
<div class="realtime-badge">
|
||||||
|
<span class="realtime-dot"></span>
|
||||||
|
{{ $c2 }} Active
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Site Block 3 -->
|
||||||
|
{{ $a3 := .Subrequest "a3" }}
|
||||||
|
{{ $s3 := .Subrequest "s3" }}
|
||||||
|
<div class="plausible-site-row">
|
||||||
|
<div class="site-icon">
|
||||||
|
<img src="https://icon.horse/icon/ayris.tech" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="site-info">
|
||||||
|
<span class="site-domain">ayris.tech</span>
|
||||||
|
<div class="site-metrics">
|
||||||
|
{{ if eq $s3.Response.StatusCode 200 }}
|
||||||
|
<span class="metric-item">Visitors: <b>{{ $s3.JSON.Get "results.visitors.value" }}</b></span>
|
||||||
|
<span class="metric-item">Views: <b>{{ $s3.JSON.Get "results.pageviews.value" }}</b></span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-status">
|
||||||
|
{{ if eq $a3.Response.StatusCode 200 }}
|
||||||
|
{{ $c3 := $a3.JSON.Raw }}
|
||||||
|
{{ if ne $c3 "0" }}
|
||||||
|
<div class="realtime-badge">
|
||||||
|
<span class="realtime-dot"></span>
|
||||||
|
{{ $c3 }} Active
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Site Block 4 -->
|
||||||
|
{{ $a4 := .Subrequest "a4" }}
|
||||||
|
{{ $s4 := .Subrequest "s4" }}
|
||||||
|
<div class="plausible-site-row">
|
||||||
|
<div class="site-icon">
|
||||||
|
<img src="https://icon.horse/icon/irisiptv.online" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="site-info">
|
||||||
|
<span class="site-domain">irisiptv.online</span>
|
||||||
|
<div class="site-metrics">
|
||||||
|
{{ if eq $s4.Response.StatusCode 200 }}
|
||||||
|
<span class="metric-item">Visitors: <b>{{ $s4.JSON.Get "results.visitors.value" }}</b></span>
|
||||||
|
<span class="metric-item">Views: <b>{{ $s4.JSON.Get "results.pageviews.value" }}</b></span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-status">
|
||||||
|
{{ if eq $a4.Response.StatusCode 200 }}
|
||||||
|
{{ $c4 := $a4.JSON.Raw }}
|
||||||
|
{{ if ne $c4 "0" }}
|
||||||
|
<div class="realtime-badge">
|
||||||
|
<span class="realtime-dot"></span>
|
||||||
|
{{ $c4 }} Active
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Site Block 5 -->
|
||||||
|
{{ $a5 := .Subrequest "a5" }}
|
||||||
|
{{ $s5 := .Subrequest "s5" }}
|
||||||
|
<div class="plausible-site-row">
|
||||||
|
<div class="site-icon">
|
||||||
|
<img src="https://icon.horse/icon/screencapr.com" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="site-info">
|
||||||
|
<span class="site-domain">screencapr.com</span>
|
||||||
|
<div class="site-metrics">
|
||||||
|
{{ if eq $s5.Response.StatusCode 200 }}
|
||||||
|
<span class="metric-item">Visitors: <b>{{ $s5.JSON.Get "results.visitors.value" }}</b></span>
|
||||||
|
<span class="metric-item">Views: <b>{{ $s5.JSON.Get "results.pageviews.value" }}</b></span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="site-status">
|
||||||
|
{{ if eq $a5.Response.StatusCode 200 }}
|
||||||
|
{{ $c5 := $a5.JSON.Raw }}
|
||||||
|
{{ if ne $c5 "0" }}
|
||||||
|
<div class="realtime-badge">
|
||||||
|
<span class="realtime-dot"></span>
|
||||||
|
{{ $c5 }} Active
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
103
config/raindrop.yml
Normal file
103
config/raindrop.yml
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
- type: custom-api
|
||||||
|
title: Raindrop Latest Links
|
||||||
|
hide-header: true
|
||||||
|
cache: 1h
|
||||||
|
url: https://api.raindrop.io/rest/v1/raindrops/0?perpage=50
|
||||||
|
headers: { Authorization: "Bearer ${RAINDROP_TOKEN}" }
|
||||||
|
template: |
|
||||||
|
{{ if .JSON.Bool "result" }}
|
||||||
|
<div class="raindrop-list">
|
||||||
|
<style>
|
||||||
|
.raindrop-item {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--color-widget-content-border);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.raindrop-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.raindrop-cover-container {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.raindrop-cover {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.raindrop-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.raindrop-title {
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--color-highlight);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
text-decoration: none;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.raindrop-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-subdue);
|
||||||
|
font-family: var(--font-family-mono);
|
||||||
|
}
|
||||||
|
.tag-badge {
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<ul class="list collapsible-container" data-collapse-after="10">
|
||||||
|
{{ range .JSON.Array "items" }}
|
||||||
|
<li class="raindrop-item">
|
||||||
|
<div class="raindrop-cover-container">
|
||||||
|
{{ $cover := .String "cover" }}
|
||||||
|
{{ if $cover }}
|
||||||
|
<img src="{{ $cover }}" class="raindrop-cover" alt="" />
|
||||||
|
{{ else }}
|
||||||
|
<svg fill="currentColor" viewBox="0 0 24 24" width="24" height="24"><path d="M4.75 3.5a1.25 1.25 0 1 0 0 2.5 1.25 1.25 0 0 0 0-2.5zM9 13l3-3 3 3M8 15v-2h8v2H8z"/></svg>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="raindrop-info">
|
||||||
|
<a href="{{ .String "link" }}" target="_blank" class="raindrop-title">
|
||||||
|
{{ .String "title" }}
|
||||||
|
</a>
|
||||||
|
<div class="raindrop-meta">
|
||||||
|
<span class="color-primary">#{{ .String "domain" }}</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span {{ .String "created" | parseRelativeTime "rfc3339" }}></span>
|
||||||
|
{{ $tags := .Array "tags" }}
|
||||||
|
{{ if $tags }}
|
||||||
|
<span>•</span>
|
||||||
|
<span class="tag-badge">#{{ (index $tags 0).String "" }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="color-negative padding-10 text-center">
|
||||||
|
No bookmarks found or API error.
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
51
config/services.yml
Normal file
51
config/services.yml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
- type: custom-api
|
||||||
|
title: Services
|
||||||
|
hide-header: true
|
||||||
|
cache: 1m
|
||||||
|
url: ${SERVICES_JSON_URL}
|
||||||
|
template: |
|
||||||
|
<div class="services-list">
|
||||||
|
<style>
|
||||||
|
.service-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--color-widget-content-border);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.service-row:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.service-icon-container {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.service-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.service-name {
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--color-highlight);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{{ range .JSON.Raw | fromJson }}
|
||||||
|
<a href="{{ .url }}" target="_blank" class="service-row">
|
||||||
|
<div class="service-icon-container">
|
||||||
|
<img src="{{ .icon }}" class="service-icon" alt="" />
|
||||||
|
</div>
|
||||||
|
<span class="service-name">{{ .title }}</span>
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
139
config/uptime.yml
Normal file
139
config/uptime.yml
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
- type: custom-api
|
||||||
|
title: Uptime Status
|
||||||
|
title-url: ${UPTIME_KUMA_URL}
|
||||||
|
hide-header: true
|
||||||
|
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: 5m
|
||||||
|
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 }}
|
||||||
|
<div class="uptime-list">
|
||||||
|
<style>
|
||||||
|
.uptime-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border-bottom: 1px solid var(--color-widget-content-border);
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.uptime-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
.monitor-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
.monitor-icon img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.monitor-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.monitor-name {
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--color-highlight);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.monitor-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-subdue);
|
||||||
|
font-family: var(--font-family-mono);
|
||||||
|
}
|
||||||
|
.status-icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{{ range .JSON.Array "publicGroupList" }}
|
||||||
|
{{ range .Array "monitorList" }}
|
||||||
|
{{ $id := .String "id" }}
|
||||||
|
{{ $name := .String "name" }}
|
||||||
|
{{ $hbPath := concat "heartbeatList." $id }}
|
||||||
|
{{ $hbArray := $hb.JSON.Array $hbPath }}
|
||||||
|
|
||||||
|
{{ $latest := "" }}
|
||||||
|
{{ range $hbArray }}{{ $latest = . }}{{ end }}
|
||||||
|
|
||||||
|
<div class="uptime-item">
|
||||||
|
<div class="monitor-icon">
|
||||||
|
{{ if eq $name "AvEmy" }} <img src="https://icon.horse/icon/aveminakarabudak.com" alt="" />
|
||||||
|
{{ else if eq $name "ayris.tech" }} <img src="https://icon.horse/icon/ayris.tech" alt="" />
|
||||||
|
{{ else if eq $name "AyrisApart" }} <img src="https://icon.horse/icon/ayrisapart.com" alt="" />
|
||||||
|
{{ else if eq $name "irisiptv.online" }} <img src="https://icon.horse/icon/irisiptv.online" alt="" />
|
||||||
|
{{ else if eq $name "screencapr.com" }} <img src="https://icon.horse/icon/screencapr.com" alt="" />
|
||||||
|
{{ else if eq $name "Portainer" }} <img src="https://icon.horse/icon/portainer.io" alt="" />
|
||||||
|
{{ else if eq $name "Backrest" }} <img src="https://icon.horse/icon/grest.dev" alt="" />
|
||||||
|
{{ else }}
|
||||||
|
<svg fill="currentColor" viewBox="0 0 24 24" width="24" height="24"><path d="M21 16.5C21 16.88 20.79 17.21 20.47 17.38L12.57 21.82C12.41 21.94 12.21 22 12 22C11.79 22 11.59 21.94 11.43 21.82L3.53 17.38C3.21 17.21 3 16.88 3 16.5V7.5C3 7.12 3.21 6.79 3.53 6.62L11.43 2.18C11.59 2.06 11.79 2 12 2C12.21 2 12.41 2.06 12.57 2.18L20.47 6.62C20.79 6.79 21 7.12 21 7.5V16.5Z"/></svg>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="monitor-info">
|
||||||
|
<a class="monitor-name" href="${UPTIME_KUMA_URL}/dashboard/{{ $id }}" target="_blank">
|
||||||
|
{{ $name }}
|
||||||
|
</a>
|
||||||
|
<div class="monitor-meta">
|
||||||
|
{{ if $latest }}
|
||||||
|
{{ if eq ($latest.String "status") "1" }}
|
||||||
|
<span class="color-positive">OK</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{{ $latest.Int "ping" }}ms</span>
|
||||||
|
{{ else }}
|
||||||
|
<span class="color-negative">DOWN</span>
|
||||||
|
{{ if $latest.Exists "msg" }}
|
||||||
|
<span>•</span>
|
||||||
|
<span class="size-tiny">{{ $latest.String "msg" }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
<span class="color-subdue">No data</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-icon">
|
||||||
|
{{ if $latest }}
|
||||||
|
{{ if eq ($latest.String "status") "1" }}
|
||||||
|
<svg fill="var(--color-positive)" 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>
|
||||||
|
{{ else }}
|
||||||
|
<svg fill="var(--color-negative)" 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>
|
||||||
|
{{ end }}
|
||||||
|
{{ else }}
|
||||||
|
<svg fill="var(--color-subdue)" 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>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
32
services.json
Normal file
32
services.json
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "Dashboard",
|
||||||
|
"url": "https://dash.ayris.tech",
|
||||||
|
"icon": "https://raw.githubusercontent.com/glance-project/glance/main/assets/logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Mail",
|
||||||
|
"url": "https://mail.ayris.tech",
|
||||||
|
"icon": "https://stalwart.io/img/logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "n8n Automation",
|
||||||
|
"url": "https://auto.ayris.tech",
|
||||||
|
"icon": "https://n8n.io/images/press/n8n-logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Vaultwarden",
|
||||||
|
"url": "https://vault.ayris.tech",
|
||||||
|
"icon": "https://raw.githubusercontent.com/dani-garcia/vaultwarden/main/resources/vaultwarden-icon.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Analytics",
|
||||||
|
"url": "https://analytics.ayris.tech",
|
||||||
|
"icon": "https://plausible.io/assets/images/icon/plausible_logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Uptime",
|
||||||
|
"url": "http://uptimekuma-uko00s44cs8cokos4cgwk8oc.65.109.236.58.sslip.io/dashboard",
|
||||||
|
"icon": "https://uptime.kuma.pet/img/icon.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user