Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ec5dc07
docs: add CHANGELOG for 1.0.0-rc.1
manieperplex Apr 19, 2026
4bf833d
fix: remove paths-ignore in release workflow
manieperplex Apr 19, 2026
957a2b0
feat: replace runtime ca cert vars with build-time EXTRA_CA_CERTS_B64…
manieperplex Apr 20, 2026
4888f47
fix: docker ca & npmrc handling, cert env generation, switch from cor…
manieperplex Apr 22, 2026
c6a08f6
feat-usability-updates (#22)
manieperplex Apr 22, 2026
7cb2e03
feat: github token auth support, optional password protection (#23)
manieperplex Apr 23, 2026
7bf2fc4
fix: parallel job in run detail dag (#24)
manieperplex Apr 23, 2026
0924c73
fix: copy public folder in docker web to include favicon (#25)
manieperplex Apr 27, 2026
814c2e6
feat: optional workflow health dashboard section (#26)
manieperplex Apr 28, 2026
eb3957b
fix: applyBump in compute next version script
manieperplex Apr 28, 2026
5fc78ef
Merge branch 'main' into develop
manieperplex Apr 28, 2026
0d67b5a
chore: merge main into develop after v1.0.0-rc.2 release
manieperplex Apr 28, 2026
7a69f22
fix: bump hono to 4.12.18 to patch bodyLimit bypass (#28)
manieperplex May 7, 2026
a50b5c9
feat: repository runner, workflow health section ui adjustments (#29)
manieperplex May 7, 2026
6da6271
feat: dependabot config (#30)
manieperplex Jun 4, 2026
a7c6d41
fix: remove registries from dependabot config, dockerfile directories
manieperplex Jun 4, 2026
fb6526d
fix: dependebaot dockerfile path
manieperplex Jun 4, 2026
5176170
fix: dependebaot dockerfile only path
manieperplex Jun 4, 2026
60d3c23
chore(gha): bump the version-all group across 1 directory with 6 upda…
dependabot[bot] Jun 4, 2026
c8f1aac
fix: dependabot grouping for dev dependencies
manieperplex Jun 4, 2026
79685a1
fix: dependabot root entry for resolve and update catalog from pnpm-w…
manieperplex Jun 4, 2026
ee10043
chore(server):(deps): bump the version-all group across 1 directory w…
dependabot[bot] Jun 4, 2026
928eb33
chore(web):(deps): bump the version-all group across 1 directory with…
dependabot[bot] Jun 4, 2026
ae0491e
chore(deps):(deps): bump the tooling group across 1 directory with 5 …
dependabot[bot] Jun 4, 2026
e7364a6
chore(docker): bump node from 24-alpine to 26-alpine in /packages/ser…
dependabot[bot] Jun 4, 2026
a997cb6
chore(docker): bump node from 24-alpine to 26-alpine in /packages/web…
dependabot[bot] Jun 4, 2026
1f43410
chore(docker): bump nginx in /packages/web (#37)
dependabot[bot] Jun 4, 2026
34b8132
feat: add docker compose labels
manieperplex Jun 5, 2026
85395b0
chore(deps):(deps-dev): bump lint-staged in the version-all group (#38)
dependabot[bot] Jun 7, 2026
aa47635
chore(gha): bump the version-all group with 3 updates (#46)
dependabot[bot] Jun 21, 2026
1f13097
chore(docker): bump nginx in /packages/web (#45)
dependabot[bot] Jun 21, 2026
b23adf6
chore(deps):(deps): bump hono (#42)
dependabot[bot] Jun 21, 2026
8a0b802
chore(deps):(deps): bump the security-all group across 1 directory wi…
dependabot[bot] Jun 21, 2026
0a19abf
chore(deps):(deps): bump vitest from 4.1.7 to 4.1.8 in the tooling gr…
dependabot[bot] Jun 21, 2026
3b8e461
chore(web):(deps): bump the version-all group across 1 directory with…
dependabot[bot] Jun 21, 2026
db933c0
chore: update vulnerable brace-expansion, esbuild, @babel/core packag…
manieperplex Jun 21, 2026
219389f
chore(server):(deps): bump the version-all group in /packages/server …
dependabot[bot] Jun 28, 2026
5335985
refactor(ci): simplify release workflow to manual version control (#51)
manieperplex Jul 3, 2026
18cbcbb
release: 1.0.0
manieperplex Jul 3, 2026
c6f5f1b
chore: merge main into develop after v1.0.0 release (#53)
manieperplex Jul 4, 2026
8d491df
fix: pagination loop for GitHub api requests, web ui workflow truncat…
manieperplex Jul 8, 2026
8541b2b
Merge branch 'main' into develop
manieperplex Jul 8, 2026
5f67709
chore(gha): bump the version-all group with 2 updates (#57)
dependabot[bot] Jul 9, 2026
11d5aed
chore(web):(deps): bump the version-all group in /packages/web with 5…
dependabot[bot] Jul 9, 2026
80e656e
chore(deps):(deps): bump the tooling group across 1 directory with 4 …
dependabot[bot] Jul 12, 2026
46eaff4
chore(docker): bump nginx in /packages/web (#65)
dependabot[bot] Aug 5, 2026
4f35e2c
chore(gha): bump the version-all group across 1 directory with 3 upda…
dependabot[bot] Aug 5, 2026
c65d246
chore(deps):(deps): bump @hono/node-server (#70)
dependabot[bot] Aug 5, 2026
b78de44
chore(deps):(deps): bump the version-all group across 1 directory wit…
dependabot[bot] Aug 5, 2026
aa6c742
chore(deps):(deps): bump the security-all group across 1 directory wi…
dependabot[bot] Aug 5, 2026
2f5bb49
chore(server):(deps-dev): bump @types/node from 26.1.0 to 26.1.2 in /…
dependabot[bot] Aug 5, 2026
530324f
chore(web):(deps): bump recharts from 3.10.0 to 3.10.1 in /packages/w…
dependabot[bot] Aug 5, 2026
78cba42
fix: brace-expansion-undici (#78)
manieperplex Aug 5, 2026
ac39e69
feat: display-last-gh-api-refresh (#79)
manieperplex Aug 6, 2026
5512f02
Merge remote-tracking branch 'origin/main' into develop
manieperplex Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/nightly-deps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 10

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

Expand Down Expand Up @@ -75,7 +75,7 @@ jobs:

- name: Create GitHub Release
if: steps.check.outputs.skip == 'false'
uses: softprops/action-gh-release@718ea10b132b3b2eba29c1007bb80653f286566b # v3.0.1
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: ${{ steps.check.outputs.tag }}
name: ${{ steps.check.outputs.tag }}
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 10

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
Expand All @@ -49,13 +49,13 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 10

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
Expand All @@ -76,13 +76,13 @@ jobs:
permissions:
contents: read
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
with:
version: 10

- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 24
cache: pnpm
Expand All @@ -107,12 +107,12 @@ jobs:
- image: web
dockerfile: packages/web/Dockerfile
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0

- name: Build ${{ matrix.image }}
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ${{ matrix.dockerfile }}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dashlight",
"version": "1.0.1",
"version": "1.1.0",
"private": true,
"scripts": {
"prepare": "husky",
Expand Down Expand Up @@ -29,7 +29,7 @@
}
},
"overrides": {
"brace-expansion": "^5.0.6",
"brace-expansion": "^5.0.9",
"esbuild": "^0.28.1",
"@babel/core": "^7.29.7"
},
Expand All @@ -39,7 +39,7 @@
},
"devDependencies": {
"husky": "^9.1.7",
"lint-staged": "^17.0.2",
"lint-staged": "^17.1.1",
"typescript": "catalog:",
"vitest": "catalog:"
}
Expand Down
16 changes: 8 additions & 8 deletions packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@
"clean": "rm -rf dist"
},
"dependencies": {
"@hono/node-server": "^2.0.5",
"hono": "^4.12.26",
"jose": "^6.2.3",
"js-yaml": "^4.2.0",
"lru-cache": "^11.5.0",
"undici": "^8.5.0"
"@hono/node-server": "^2.0.12",
"hono": "^4.12.34",
"jose": "^6.2.4",
"js-yaml": "^5.2.2",
"lru-cache": "^11.5.2",
"undici": "^8.9.0"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^25.9.1",
"@types/node": "^26.1.2",
"@vitest/coverage-v8": "catalog:",
"eslint": "catalog:",
"tsx": "^4.22.3",
"tsx": "^4.23.1",
"typescript": "catalog:",
"typescript-eslint": "catalog:",
"vitest": "catalog:"
Expand Down
2 changes: 1 addition & 1 deletion packages/web/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ RUN pnpm --filter @dashlight/web build

# ── Runtime stage (Nginx) ─────────────────────────────────────────────────────
# The nginx runner serves only static files — no outbound HTTPS, no cert bundle needed.
FROM nginx:1.31.2-alpine AS runner
FROM nginx:1.31.3-alpine AS runner

COPY --from=builder /app/packages/web/dist /usr/share/nginx/html
COPY packages/web/nginx.conf /etc/nginx/conf.d/default.conf
Expand Down
26 changes: 13 additions & 13 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,33 @@
"clean": "rm -rf dist"
},
"dependencies": {
"@tanstack/query-async-storage-persister": "^5.100.11",
"@tanstack/react-query": "^5.100.11",
"@tanstack/react-query-persist-client": "^5.100.11",
"@tanstack/react-router": "^1.170.15",
"@xyflow/react": "^12.11.0",
"@tanstack/query-async-storage-persister": "^5.101.4",
"@tanstack/react-query": "^5.101.4",
"@tanstack/react-query-persist-client": "^5.101.4",
"@tanstack/react-router": "^1.170.18",
"@xyflow/react": "^12.11.1",
"@dagrejs/dagre": "^3.0.0",
"idb-keyval": "^6.2.4",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"recharts": "^3.8.1",
"idb-keyval": "^6.3.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"recharts": "^3.10.1",
"zod": "^4.4.3"
},
"devDependencies": {
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/router-vite-plugin": "^1.167.18",
"@tanstack/router-vite-plugin": "^1.167.23",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"@vitejs/plugin-react": "^6.0.4",
"@vitest/coverage-v8": "catalog:",
"eslint": "catalog:",
"eslint-plugin-react-hooks": "^7.1.1",
"jsdom": "^29.1.1",
"jsdom": "^30.0.1",
"typescript": "catalog:",
"typescript-eslint": "catalog:",
"vite": "^8.0.14",
"vite": "^8.1.5",
"vitest": "catalog:"
}
}
86 changes: 84 additions & 2 deletions packages/web/src/api/client.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import { fetchApi, fetchApiText, ApiError } from "./client.js"
import { fetchApi, fetchApiText, ApiError, getLastGitHubFetchAt, subscribeLastGitHubFetchAt } from "./client.js"

function mockFetch(status: number, body: unknown, ok = status >= 200 && status < 300) {
function mockFetch(
status: number,
body: unknown,
ok = status >= 200 && status < 300,
headers?: Record<string, string>,
) {
return vi.fn().mockResolvedValue({
ok,
status,
headers: new Headers(headers),
json: () => Promise.resolve(body),
text: () => Promise.resolve(typeof body === "string" ? body : JSON.stringify(body)),
})
Expand Down Expand Up @@ -101,3 +107,79 @@ describe("fetchApiText", () => {
await expect(fetchApiText("/api/logs")).rejects.toThrow("HTTP 404")
})
})

// ── X-Cache tracking (lastGitHubFetchAt) ─────────────────────────────────────

describe("lastGitHubFetchAt tracking", () => {
it("updates on X-Cache: MISS from fetchApi", async () => {
const before = Date.now()
vi.stubGlobal("fetch", mockFetch(200, { ok: true }, true, { "X-Cache": "MISS" }))
await fetchApi("/proxy/repos/owner/repo/actions/runs")
const ts = getLastGitHubFetchAt()
expect(ts).toBeGreaterThanOrEqual(before)
expect(ts).toBeLessThanOrEqual(Date.now())
})

it("does not update on X-Cache: HIT from fetchApi", async () => {
// First set a baseline with a MISS
vi.stubGlobal("fetch", mockFetch(200, {}, true, { "X-Cache": "MISS" }))
await fetchApi("/proxy/repos/owner/repo")
const baseline = getLastGitHubFetchAt()

// Wait a tick so any new timestamp would differ
await new Promise((r) => setTimeout(r, 5))

// HIT should not change the timestamp
vi.stubGlobal("fetch", mockFetch(200, {}, true, { "X-Cache": "HIT" }))
await fetchApi("/proxy/repos/owner/repo")
expect(getLastGitHubFetchAt()).toBe(baseline)
})

it("does not update when no X-Cache header is present", async () => {
vi.stubGlobal("fetch", mockFetch(200, {}, true, { "X-Cache": "MISS" }))
await fetchApi("/proxy/repos/owner/repo")
const baseline = getLastGitHubFetchAt()

await new Promise((r) => setTimeout(r, 5))

// No X-Cache header (e.g. /auth/config)
vi.stubGlobal("fetch", mockFetch(200, {}))
await fetchApi("/auth/config")
expect(getLastGitHubFetchAt()).toBe(baseline)
})

it("updates on X-Cache: MISS from fetchApiText", async () => {
const before = Date.now()
vi.stubGlobal("fetch", mockFetch(200, "log data", true, { "X-Cache": "MISS" }))
await fetchApiText("/proxy/repos/owner/repo/actions/jobs/1/logs")
const ts = getLastGitHubFetchAt()
expect(ts).toBeGreaterThanOrEqual(before)
})

it("notifies subscribers on cache miss", async () => {
const listener = vi.fn()
const unsub = subscribeLastGitHubFetchAt(listener)

vi.stubGlobal("fetch", mockFetch(200, {}, true, { "X-Cache": "MISS" }))
await fetchApi("/proxy/repos/owner/repo")
expect(listener).toHaveBeenCalledOnce()

// HIT should not notify
listener.mockClear()
vi.stubGlobal("fetch", mockFetch(200, {}, true, { "X-Cache": "HIT" }))
await fetchApi("/proxy/repos/owner/repo")
expect(listener).not.toHaveBeenCalled()

unsub()
})

it("unsubscribe stops notifications", async () => {
const listener = vi.fn()
const unsub = subscribeLastGitHubFetchAt(listener)
unsub()

vi.stubGlobal("fetch", mockFetch(200, {}, true, { "X-Cache": "MISS" }))
await fetchApi("/proxy/repos/owner/repo")
expect(listener).not.toHaveBeenCalled()
})
})
25 changes: 25 additions & 0 deletions packages/web/src/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
// Tracks when the server last fetched fresh data from GitHub (X-Cache: MISS).
let _lastGitHubFetchAt: number | null = null
const _listeners = new Set<() => void>()

export function getLastGitHubFetchAt(): number | null {
return _lastGitHubFetchAt
}

export function subscribeLastGitHubFetchAt(listener: () => void): () => void {
_listeners.add(listener)
return () => _listeners.delete(listener)
}

function recordCacheMiss(): void {
_lastGitHubFetchAt = Date.now()
for (const listener of _listeners) listener()
}

export class ApiError extends Error {
constructor(
message: string,
Expand Down Expand Up @@ -36,6 +54,10 @@ export async function fetchApi<T>(
throw new ApiError(message, res.status, body)
}

if (res.headers.get("X-Cache") === "MISS") {
recordCacheMiss()
}

if (res.status === 204) {
return null as unknown as T
}
Expand All @@ -49,5 +71,8 @@ export async function fetchApiText(path: string, options?: RequestInit): Promise
credentials: "include",
})
if (!res.ok) throw new ApiError(`HTTP ${res.status}`, res.status)
if (res.headers.get("X-Cache") === "MISS") {
recordCacheMiss()
}
return res.text()
}
Loading
Loading