Skip to content

Commit 49fb73a

Browse files
authored
Merge pull request #1 from steveiliop56/feat/dashboard
feat: dashboard
2 parents a19b43a + 664fb7e commit 49fb73a

11 files changed

Lines changed: 758 additions & 5 deletions

File tree

.github/workflows/release.yml

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,54 @@ jobs:
5353
if-no-files-found: error
5454
retention-days: 1
5555

56+
image-build-dashboard:
57+
runs-on: ubuntu-latest
58+
steps:
59+
- name: Checkout
60+
uses: actions/checkout@v4
61+
62+
- name: Docker meta
63+
id: meta
64+
uses: docker/metadata-action@v5
65+
with:
66+
images: ghcr.io/${{ github.repository_owner }}/tinyauth-analytics-dashboard
67+
68+
- name: Login to GitHub Container Registry
69+
uses: docker/login-action@v3
70+
with:
71+
registry: ghcr.io
72+
username: ${{ github.repository_owner }}
73+
password: ${{ secrets.GITHUB_TOKEN }}
74+
75+
- name: Set up Docker Buildx
76+
uses: docker/setup-buildx-action@v3
77+
78+
- name: Build and push
79+
uses: docker/build-push-action@v6
80+
id: build
81+
with:
82+
platforms: linux/amd64
83+
labels: ${{ steps.meta.outputs.labels }}
84+
tags: ghcr.io/${{ github.repository_owner }}/tinyauth-analytics-dashboard
85+
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
86+
context: ./dashboard
87+
build-args: |
88+
VERSION=${{ github.ref_name }}
89+
90+
- name: Export digest
91+
run: |
92+
mkdir -p ${{ runner.temp }}/digests
93+
digest="${{ steps.build.outputs.digest }}"
94+
touch "${{ runner.temp }}/digests/${digest#sha256:}"
95+
96+
- name: Upload digest
97+
uses: actions/upload-artifact@v4
98+
with:
99+
name: digests-dashboard-linux-amd64
100+
path: ${{ runner.temp }}/digests/*
101+
if-no-files-found: error
102+
retention-days: 1
103+
56104
image-build-arm:
57105
runs-on: ubuntu-24.04-arm
58106
steps:
@@ -100,6 +148,54 @@ jobs:
100148
if-no-files-found: error
101149
retention-days: 1
102150

151+
image-build-arm-dashboard:
152+
runs-on: ubuntu-24.04-arm
153+
steps:
154+
- name: Checkout
155+
uses: actions/checkout@v4
156+
157+
- name: Docker meta
158+
id: meta
159+
uses: docker/metadata-action@v5
160+
with:
161+
images: ghcr.io/${{ github.repository_owner }}/tinyauth-analytics-dashboard
162+
163+
- name: Login to GitHub Container Registry
164+
uses: docker/login-action@v3
165+
with:
166+
registry: ghcr.io
167+
username: ${{ github.repository_owner }}
168+
password: ${{ secrets.GITHUB_TOKEN }}
169+
170+
- name: Set up Docker Buildx
171+
uses: docker/setup-buildx-action@v3
172+
173+
- name: Build and push
174+
uses: docker/build-push-action@v6
175+
id: build
176+
with:
177+
platforms: linux/arm64
178+
labels: ${{ steps.meta.outputs.labels }}
179+
tags: ghcr.io/${{ github.repository_owner }}/tinyauth-analytics-dashboard
180+
outputs: type=image,push-by-digest=true,name-canonical=true,push=true
181+
context: ./dashboard
182+
build-args: |
183+
VERSION=${{ github.ref_name }}
184+
185+
- name: Export digest
186+
run: |
187+
mkdir -p ${{ runner.temp }}/digests
188+
digest="${{ steps.build.outputs.digest }}"
189+
touch "${{ runner.temp }}/digests/${digest#sha256:}"
190+
191+
- name: Upload digest
192+
uses: actions/upload-artifact@v4
193+
with:
194+
name: digests-dashboard-linux-arm64
195+
path: ${{ runner.temp }}/digests/*
196+
if-no-files-found: error
197+
retention-days: 1
198+
103199
image-merge:
104200
runs-on: ubuntu-latest
105201
needs:
@@ -138,3 +234,42 @@ jobs:
138234
run: |
139235
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
140236
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth-analytics@sha256:%s ' *)
237+
238+
image-merge-dashboard:
239+
runs-on: ubuntu-latest
240+
needs:
241+
- image-build-dashboard
242+
- image-build-arm-dashboard
243+
steps:
244+
- name: Download digests
245+
uses: actions/download-artifact@v4
246+
with:
247+
path: ${{ runner.temp }}/digests
248+
pattern: digests-dashboard*
249+
merge-multiple: true
250+
251+
- name: Login to GitHub Container Registry
252+
uses: docker/login-action@v3
253+
with:
254+
registry: ghcr.io
255+
username: ${{ github.repository_owner }}
256+
password: ${{ secrets.GITHUB_TOKEN }}
257+
258+
- name: Set up Docker Buildx
259+
uses: docker/setup-buildx-action@v3
260+
261+
- name: Docker meta
262+
id: meta
263+
uses: docker/metadata-action@v5
264+
with:
265+
images: ghcr.io/${{ github.repository_owner }}/tinyauth-analytics-dashboard
266+
tags: |
267+
type=semver,pattern={{version}},prefix=v
268+
type=semver,pattern={{major}},prefix=v
269+
type=semver,pattern={{major}}.{{minor}},prefix=v
270+
271+
- name: Create manifest list and push
272+
working-directory: ${{ runner.temp }}/digests
273+
run: |
274+
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
275+
$(printf 'ghcr.io/${{ github.repository_owner }}/tinyauth-analytics-dashboard@sha256:%s ' *)

dashboard/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# build out
2+
dashboard

dashboard/Dockerfile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Builder
2+
FROM golang:1.25-alpine3.21 AS builder
3+
4+
ARG VERSION
5+
6+
WORKDIR /analytics-dashboard
7+
8+
COPY go.mod ./
9+
10+
RUN go mod download
11+
12+
COPY ./main.go ./
13+
COPY ./dashboard.html ./
14+
COPY ./favicon.ico ./
15+
16+
RUN CGO_ENABLED=0 go build -o analytics-dashboard -ldflags "-s -w -X main.version=${VERSION}"
17+
18+
# Runner
19+
FROM alpine:3.22 AS runner
20+
21+
WORKDIR /analytics-dashboard
22+
23+
COPY --from=builder /analytics-dashboard/analytics-dashboard ./
24+
25+
RUN adduser -u 1000 -H -D analytics-dashboard
26+
27+
EXPOSE 8080
28+
29+
USER analytics-dashboard
30+
31+
ENV PATH=$PATH:/analytics-dashboard
32+
33+
ENTRYPOINT ["analytics-dashboard"]

dashboard/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Dashboard
2+
3+
A simple server that periodically fetches data from the analytics server API and displays them in a simple dashboard.
4+
5+
## Usage
6+
7+
Build the binary with:
8+
9+
```sh
10+
go build .
11+
```
12+
13+
And run with:
14+
15+
```
16+
./dashboard
17+
```
18+
19+
Then visit <http://localhost:8080> to see the analytics.
20+
21+
> [!NOTE]
22+
> A docker image is also available, check out the example [docker compose](../docker-compose.yml) file.
23+
24+
## Configuration
25+
26+
You can configure the server using environment variables, the following options are supported:
27+
28+
| Name | Type | Description | Default |
29+
| ------------------ | ------ | ------------------------------------------------ | -------------------------- |
30+
| `PORT` | number | The port to run the server on. | `8080` |
31+
| `ADDRESS` | string | The address to bind the server to. | `0.0.0.0` |
32+
| `API_SERVER` | string | The analytics API server URL to fetch data from. | `https://api.tinyauth.app` |
33+
| `PAGE_SIZE` | number | Number of instances to display per page. | `10` |
34+
| `REFRESH_INTERVAL` | number | How often to refresh data from API (in minutes). | `30` |

dashboard/dashboard.html

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<meta name="description" content="Tinyauth analytics dashboard" />
7+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
8+
<title>Dashboard</title>
9+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
10+
<style type="text/tailwindcss">
11+
@theme {
12+
--color-fd-background: #121212;
13+
--color-fd-foreground: #ebebeb;
14+
--color-fd-muted: #212121;
15+
--color-fd-muted-foreground: #b3b3b3cc;
16+
--color-fd-popover: #1e1e1e;
17+
--color-fd-popover-foreground: #dedede;
18+
--color-fd-card: #191919;
19+
--color-fd-card-foreground: #fafafa;
20+
--color-fd-border: #6663;
21+
--color-fd-primary: #fafafa;
22+
--color-fd-primary-foreground: #171717;
23+
--color-fd-secondary: #212121;
24+
--color-fd-secondary-foreground: #ebebeb;
25+
--color-fd-accent: #6868684d;
26+
--color-fd-accent-foreground: #e6e6e6;
27+
--color-fd-ring: #8c8c8c;
28+
}
29+
</style>
30+
<style>
31+
:root {
32+
color-scheme: dark;
33+
}
34+
</style>
35+
</head>
36+
37+
<body
38+
class="flex flex-col font-sans bg-fd-background text-fd-foreground min-h-screen"
39+
>
40+
<header class="border-b border-fd-border">
41+
<div class="flex flex-row justify-between items-center px-4 h-14">
42+
<div class="flex flex-row gap-1.5 items-center">
43+
<img
44+
src="https://tinyauth.app/icon/logo.png"
45+
alt="Tinyauth Logo"
46+
class="size-5"
47+
/>
48+
<a
49+
href="https://tinyauth.app"
50+
target="_blank"
51+
rel="noreferrer"
52+
class="font-semibold"
53+
>Tinyauth</a
54+
>
55+
</div>
56+
<div class="flex flex-row gap-1.5 items-center">
57+
<a
58+
href="https://github.com/steveiliop56/tinyauth-analytics"
59+
target="_blank"
60+
rel="noreferrer"
61+
aria-label="GitHub Repository"
62+
class="rounded-full border p-1 hover:opacity-80 transform-colors delay-100 border-fd-border hover:cursor-pointer"
63+
>
64+
<svg
65+
xmlns="http://www.w3.org/2000/svg"
66+
width="24"
67+
height="24"
68+
viewBox="0 0 24 24"
69+
class="fill-fd-muted-foreground"
70+
>
71+
<path
72+
d="M12 2A10 10 0 0 0 2 12c0 4.42 2.87 8.17 6.84 9.5c.5.08.66-.23.66-.5v-1.69c-2.77.6-3.36-1.34-3.36-1.34c-.46-1.16-1.11-1.47-1.11-1.47c-.91-.62.07-.6.07-.6c1 .07 1.53 1.03 1.53 1.03c.87 1.52 2.34 1.07 2.91.83c.09-.65.35-1.09.63-1.34c-2.22-.25-4.55-1.11-4.55-4.92c0-1.11.38-2 1.03-2.71c-.1-.25-.45-1.29.1-2.64c0 0 .84-.27 2.75 1.02c.79-.22 1.65-.33 2.5-.33s1.71.11 2.5.33c1.91-1.29 2.75-1.02 2.75-1.02c.55 1.35.2 2.39.1 2.64c.65.71 1.03 1.6 1.03 2.71c0 3.82-2.34 4.66-4.57 4.91c.36.31.69.92.69 1.85V21c0 .27.16.59.67.5C19.14 20.16 22 16.42 22 12A10 10 0 0 0 12 2"
73+
/>
74+
</svg>
75+
</a>
76+
</div>
77+
</div>
78+
</header>
79+
<main class="flex flex-col flex-1 p-4 gap-4">
80+
<div class="grid grid-cols-2 gap-4">
81+
<div
82+
class="flex flex-col gap-2 p-4 border border-fd-border rounded-md items-center justify-center"
83+
>
84+
<div class="font-bold text-xl md:text-2xl text-center">
85+
Total instances
86+
</div>
87+
<div
88+
class="text-2xl md:text-3xl font-black text-fd-accent-foreground"
89+
>
90+
{{.TotalInstances}}
91+
</div>
92+
</div>
93+
<div
94+
class="flex flex-col gap-2 p-4 border border-fd-border rounded-md items-center justify-center"
95+
>
96+
<div class="font-bold text-xl md:text-2xl text-center">
97+
Most used version
98+
</div>
99+
<div
100+
class="text-2xl md:text-3xl font-black text-fd-accent-foreground"
101+
>
102+
{{.MostUsedVersion}}
103+
</div>
104+
</div>
105+
</div>
106+
<div
107+
class="flex flex-1 flex-col gap-4 p-4 border border-fd-border rounded-md"
108+
>
109+
<div class="overflow-auto text-nowrap">
110+
<table class="rounded-t-md border border-fd-border w-full">
111+
<thead class="bg-fd-card">
112+
<tr class="border-b border-fd-border">
113+
<th class="text-start p-4 border-r border-fd-border">UUID</th>
114+
<th class="text-start p-4 border-r border-fd-border">
115+
Version
116+
</th>
117+
<th class="text-start p-4 border-r border-fd-border">
118+
Last Seen
119+
</th>
120+
</tr>
121+
</thead>
122+
<tbody>
123+
{{range .Instances}}
124+
<tr class="border-b border-fd-border" id="instance-{{.UUID}}">
125+
<td class="p-4 border-r border-fd-border">{{.UUID}}</td>
126+
<td class="p-4 border-r border-fd-border">{{.Version}}</td>
127+
<td
128+
class="p-4 border-r border-fd-border"
129+
id="instance-{{.UUID}}-time"
130+
>
131+
{{.LastSeen}}
132+
</td>
133+
</tr>
134+
{{end}}
135+
</tbody>
136+
</table>
137+
</div>
138+
{{if lt .NextPage .MaxPages }}
139+
<a
140+
href="/?page={{.NextPage}}"
141+
class="text-center text-fd-muted-foreground text-md hover:underline hover:cursor-pointer"
142+
>Load more...</a
143+
>
144+
{{end}}
145+
</div>
146+
</main>
147+
<footer
148+
class="w-full border-t border-fd-border bg-fd-card items-center justify-center p-6"
149+
>
150+
<p class="text-md font-medium text-center">
151+
Copyright &copy; 2025 Tinyauth
152+
</p>
153+
</footer>
154+
</body>
155+
156+
<script>
157+
window.onload = function () {
158+
const instances = document.querySelectorAll("tr[id^='instance-']");
159+
instances.forEach((instance) => {
160+
const timeCell = instance.querySelector("td[id$='-time']");
161+
const timestamp = timeCell.textContent;
162+
const date = new Date(Number.parseInt(timestamp));
163+
const formattedDate = date.toLocaleString();
164+
timeCell.textContent = formattedDate;
165+
});
166+
};
167+
</script>
168+
</html>

dashboard/favicon.ico

14.7 KB
Binary file not shown.

dashboard/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module dashboard
2+
3+
go 1.24.3

0 commit comments

Comments
 (0)