thin RustyWolf window chrome; make heatmap proxy degrade gracefully on upstream failure
This commit is contained in:
parent
7f6ac177b4
commit
6daaf7e55b
8 changed files with 52 additions and 34 deletions
|
|
@ -76,7 +76,7 @@ more.
|
||||||
- **Fonts:** the web theme uses the system sans stack; Monocraft (self-hosted in `public/`) is
|
- **Fonts:** the web theme uses the system sans stack; Monocraft (self-hosted in `public/`) is
|
||||||
used by the desktop theme's SMP menu and the in-desktop terminal.
|
used by the desktop theme's SMP menu and the in-desktop terminal.
|
||||||
- **Images:** `next/image`
|
- **Images:** `next/image`
|
||||||
- **Version:** `2.2.9` (see `package.json`)
|
- **Version:** `2.2.10` (see `package.json`)
|
||||||
- **Dependencies:** `npm audit` reports **0 vulnerabilities**. `next` is kept at a patched
|
- **Dependencies:** `npm audit` reports **0 vulnerabilities**. `next` is kept at a patched
|
||||||
release (`>=16.3.6`) for the `next/og` RCE, and `brace-expansion` is force-upgraded via
|
release (`>=16.3.6`) for the `next/og` RCE, and `brace-expansion` is force-upgraded via
|
||||||
`overrides`. `braces` (a transitive ESLint/`fast-glob` dependency) has **no upstream fix**,
|
`overrides`. `braces` (a transitive ESLint/`fast-glob` dependency) has **no upstream fix**,
|
||||||
|
|
@ -172,7 +172,7 @@ they contend for `:3000` and `.next/dev/lock`.
|
||||||
|
|
||||||
| Route | Method | What it does |
|
| Route | Method | What it does |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `/api/heatmap` | `GET` | Proxies the user's contribution heatmap from `git.dvorakmortonxd.dev`. |
|
| `/api/heatmap` | `GET` | Proxies the user's contribution heatmap from `git.dvorakmortonxd.dev`. Returns `502` (and the panel shows a friendly fallback) if the upstream is unreachable. |
|
||||||
| `/api/activities` | `GET` | Fetches the GitDXD activity feed and keeps only the user's own events. |
|
| `/api/activities` | `GET` | Fetches the GitDXD activity feed and keeps only the user's own events. |
|
||||||
| `/api/frame?url=…` | `GET` | Header-stripping proxy for a friend's site that refuses framing (`skopec.dev`). **Allow-listed hosts only**; fetches the HTML server-side (so the site's `X-Frame-Options` never reaches the browser), injects a `<base>` for relative assets and a click shim for in-site links. Rejects other hosts, non-http(s) schemes and credentials, and never follows redirects. |
|
| `/api/frame?url=…` | `GET` | Header-stripping proxy for a friend's site that refuses framing (`skopec.dev`). **Allow-listed hosts only**; fetches the HTML server-side (so the site's `X-Frame-Options` never reaches the browser), injects a `<base>` for relative assets and a click shim for in-site links. Rejects other hosts, non-http(s) schemes and credentials, and never follows redirects. |
|
||||||
| `/api/ping?url=…` | `GET` | Health-checks an **allow-listed** service (the site's own `*.dvorakmortonxd.dev` hosts and the friends' sites). Handles `minecraft://host:port` via mcstatus.io; treats any non-5xx response as "up". Other hosts and schemes are rejected and redirects are never followed, so it can't be used to probe the LAN (SSRF guard). |
|
| `/api/ping?url=…` | `GET` | Health-checks an **allow-listed** service (the site's own `*.dvorakmortonxd.dev` hosts and the friends' sites). Handles `minecraft://host:port` via mcstatus.io; treats any non-5xx response as "up". Other hosts and schemes are rejected and redirects are never followed, so it can't be used to probe the LAN (SSRF guard). |
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ When in doubt, prefer updating. A stale README **or** agents.md is treated as a
|
||||||
## Bump the version on EVERY prompt — also do this every time
|
## Bump the version on EVERY prompt — also do this every time
|
||||||
|
|
||||||
Another standing requirement: increment the **patch** version (`x.x.+1`, so
|
Another standing requirement: increment the **patch** version (`x.x.+1`, so
|
||||||
`2.2.8` → `2.2.9` → …) as part of every task, even the small ones. Keep all of
|
`2.2.9` → `2.2.10` → …) as part of every task, even the small ones. Keep all of
|
||||||
these in sync:
|
these in sync:
|
||||||
|
|
||||||
- `package.json` → `"version"`
|
- `package.json` → `"version"`
|
||||||
|
|
@ -56,7 +56,7 @@ frosted-glass **web** site, and an alternate KDE/Plasma **desktop simulation**
|
||||||
utilities in the components (Tailwind is present via PostCSS but the look is
|
utilities in the components (Tailwind is present via PostCSS but the look is
|
||||||
hand-written CSS).
|
hand-written CSS).
|
||||||
- Path alias: `@/*` → `src/*`.
|
- Path alias: `@/*` → `src/*`.
|
||||||
- Version: `2.2.9` (see `package.json`).
|
- Version: `2.2.10` (see `package.json`).
|
||||||
|
|
||||||
### Scripts
|
### Scripts
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -96,6 +96,7 @@ src/app/
|
||||||
revival-inc/page.tsx → fullscreen iframe embedding /public/revival-inc/index.html
|
revival-inc/page.tsx → fullscreen iframe embedding /public/revival-inc/index.html
|
||||||
api/
|
api/
|
||||||
heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap
|
heatmap/route.ts → proxy for git.dvorakmortonxd.dev contribution heatmap
|
||||||
|
(try/catch → 502 on upstream failure; the widget degrades to a fallback)
|
||||||
activities/route.ts → proxy for the user's GitDXD activity feed
|
activities/route.ts → proxy for the user's GitDXD activity feed
|
||||||
ping/route.ts → service health check (HTTP + minecraft:// via mcstatus.io); host allowlist
|
ping/route.ts → service health check (HTTP + minecraft:// via mcstatus.io); host allowlist
|
||||||
frame/route.ts → header-stripping proxy so an unframeable friend site loads in a window; host allowlist
|
frame/route.ts → header-stripping proxy so an unframeable friend site loads in a window; host allowlist
|
||||||
|
|
|
||||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "dvorakmortonxd",
|
"name": "dvorakmortonxd",
|
||||||
"version": "2.2.9",
|
"version": "2.2.10",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dvorakmortonxd",
|
"name": "dvorakmortonxd",
|
||||||
"version": "2.2.9",
|
"version": "2.2.10",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"next": "^16.3.8",
|
"next": "^16.3.8",
|
||||||
"react": "^19.3.0",
|
"react": "^19.3.0",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "dvorakmortonxd",
|
"name": "dvorakmortonxd",
|
||||||
"version": "2.2.9",
|
"version": "2.2.10",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev-server.mjs",
|
"dev": "node scripts/dev-server.mjs",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,15 @@
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const res = await fetch("https://git.dvorakmortonxd.dev/api/v1/users/dvorakmortonxd/heatmap");
|
try {
|
||||||
const data = await res.json();
|
const res = await fetch(
|
||||||
return Response.json(data);
|
"https://git.dvorakmortonxd.dev/api/v1/users/dvorakmortonxd/heatmap",
|
||||||
}
|
{ headers: { Accept: "application/json" } }
|
||||||
|
);
|
||||||
|
if (!res.ok) {
|
||||||
|
return Response.json({ error: "failed to load" }, { status: 502 });
|
||||||
|
}
|
||||||
|
const data = await res.json();
|
||||||
|
return Response.json(data);
|
||||||
|
} catch {
|
||||||
|
return Response.json({ error: "failed to load" }, { status: 502 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,9 +39,16 @@ export default function ContributionHeatmap({ variant = "terminal" }: { variant?
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/heatmap");
|
const res = await fetch("/api/heatmap");
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new Error("Failed to fetch");
|
setError(true);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const data: HeatmapData[] = await res.json();
|
const data: HeatmapData[] = await res.json();
|
||||||
|
if (!Array.isArray(data)) {
|
||||||
|
setError(true);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const map = new Map<string, number>();
|
const map = new Map<string, number>();
|
||||||
let total = 0;
|
let total = 0;
|
||||||
let maxContribs = 0;
|
let maxContribs = 0;
|
||||||
|
|
@ -90,7 +97,7 @@ export default function ContributionHeatmap({ variant = "terminal" }: { variant?
|
||||||
setTotalContribs(total);
|
setTotalContribs(total);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load heatmap:", err);
|
console.warn("Failed to load heatmap:", err);
|
||||||
setError(true);
|
setError(true);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -106,7 +113,7 @@ export default function ContributionHeatmap({ variant = "terminal" }: { variant?
|
||||||
setActivities(valid);
|
setActivities(valid);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Failed to load activities:", err);
|
console.warn("Failed to load activities:", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,7 @@ export default function WebLayout() {
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<footer className="web-footer glass">
|
<footer className="web-footer glass">
|
||||||
<span className="web-footer-label">v2.2.9</span>
|
<span className="web-footer-label">v2.2.10</span>
|
||||||
© 2025-2026 Martin Dvořák. All rights reserved (not really I dont care, just
|
© 2025-2026 Martin Dvořák. All rights reserved (not really I dont care, just
|
||||||
follow the GPL3.0 License)
|
follow the GPL3.0 License)
|
||||||
</footer>
|
</footer>
|
||||||
|
|
|
||||||
|
|
@ -649,44 +649,44 @@ html[data-theme="web"] body {
|
||||||
.fw-tabs {
|
.fw-tabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
gap: 6px;
|
gap: 5px;
|
||||||
height: 32px;
|
height: 26px;
|
||||||
padding: 7px 10px 0;
|
padding: 5px 10px 0;
|
||||||
}
|
}
|
||||||
.fw-tab {
|
.fw-tab {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 7px;
|
gap: 6px;
|
||||||
max-width: 62%;
|
max-width: 62%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
padding: 6px 13px;
|
padding: 3px 11px;
|
||||||
border-radius: 9px 9px 0 0;
|
border-radius: 8px 8px 0 0;
|
||||||
border: 1px solid var(--btn-border, var(--glass-border));
|
border: 1px solid var(--btn-border, var(--glass-border));
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
background: var(--btn-bg, var(--glass-lo));
|
background: var(--btn-bg, var(--glass-lo));
|
||||||
font-size: 0.66rem;
|
font-size: 0.62rem;
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
.fw-tab-dot {
|
.fw-tab-dot {
|
||||||
flex: none;
|
flex: none;
|
||||||
width: 8px;
|
width: 7px;
|
||||||
height: 8px;
|
height: 7px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--accent);
|
background: var(--accent);
|
||||||
}
|
}
|
||||||
.fw-tab-plus {
|
.fw-tab-plus {
|
||||||
padding: 0 6px;
|
padding: 0 5px;
|
||||||
color: var(--web-muted);
|
color: var(--web-muted);
|
||||||
font-size: 0.9rem;
|
font-size: 0.82rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.fw-toolbar {
|
.fw-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 6px;
|
||||||
padding: 8px 12px;
|
padding: 5px 10px;
|
||||||
border-top: 1px solid var(--glass-border);
|
border-top: 1px solid var(--glass-border);
|
||||||
border-bottom: 1px solid var(--glass-border);
|
border-bottom: 1px solid var(--glass-border);
|
||||||
background: var(--fw-chrome);
|
background: var(--fw-chrome);
|
||||||
|
|
@ -695,12 +695,12 @@ html[data-theme="web"] body {
|
||||||
flex: none;
|
flex: none;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
width: 22px;
|
width: 20px;
|
||||||
height: 22px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--btn-bg, var(--glass-lo));
|
background: var(--btn-bg, var(--glass-lo));
|
||||||
color: var(--web-muted);
|
color: var(--web-muted);
|
||||||
font-size: 0.85rem;
|
font-size: 0.78rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.fw-url {
|
.fw-url {
|
||||||
|
|
@ -708,13 +708,13 @@ html[data-theme="web"] body {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
height: 26px;
|
height: 22px;
|
||||||
padding: 0 12px;
|
padding: 0 11px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
border: 1px solid var(--glass-border);
|
border: 1px solid var(--glass-border);
|
||||||
background: var(--glass-lo);
|
background: var(--glass-lo);
|
||||||
font-family: var(--font-custom-mono);
|
font-family: var(--font-custom-mono);
|
||||||
font-size: 0.66rem;
|
font-size: 0.62rem;
|
||||||
color: var(--web-muted);
|
color: var(--web-muted);
|
||||||
}
|
}
|
||||||
.fw-lock {
|
.fw-lock {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue